E-commerce

Course Detail Page

A premium Flutter course detail page with a 16:9 hero banner, floating glassy back/share buttons, category pill, rating row, mentor chip, switchable About/Curriculum/Reviews tabs, lesson cards with locked/unlocked states and a sticky bottom price + Enroll Now CTA. Drops straight into any e-learning, masterclass or bootcamp app.

e-learningdetaillessons

WHAT'S INCLUDED

  • Hero image with gradient overlay and floating back / share glass buttons
  • Category pill, large title and rating + mentor chip row
  • Switchable About / Curriculum / Reviews tabs with animated underline
  • Curriculum lesson list with play-circle icons, durations and lock states
  • Sticky bottom bar with total price and full-width Enroll Now CTA
  • Pure Flutter SDK widgets — no external packages, easy to theme

USE CASES

  • Course detail screen for any online learning app (Udemy, Coursera style)
  • Masterclass or live cohort detail page in a creator-led platform
  • Bootcamp / skill track detail in an upskilling app
  • Paid workshop detail in a community or membership app

PRO TIP

Wrap the body in a CustomScrollView with a SliverAppBar (expandedHeight matching the hero) so the hero image pins and parallaxes as you scroll, while the sticky Enroll bar stays anchored. Drive the tab content with an IndexedStack instead of swapping widgets — it preserves scroll position when the user switches between About, Curriculum and Reviews.

Complete Dart Code

Copy this into your Flutter project. No external packages required.

dart
import 'package:flutter/material.dart';

class CourseDetailPage extends StatefulWidget {
  const CourseDetailPage({super.key});

  @override
  State<CourseDetailPage> createState() => _CourseDetailPageState();
}

class _CourseDetailPageState extends State<CourseDetailPage> {
  static const Color _primary = Color(0xFF0042DE);
  static const Color _primaryContainer = Color(0xFF335EF7);
  static const Color _surface = Color(0xFFFBF8FF);
  static const Color _surfaceCard = Color(0xFFFFFFFF);
  static const Color _surfaceContainerLow = Color(0xFFF3F2FF);
  static const Color _surfaceContainer = Color(0xFFEDEDFA);
  static const Color _textPrimary = Color(0xFF111827);
  static const Color _textMuted = Color(0xFF6B7280);
  static const Color _textBody = Color(0xFF434655);
  static const Color _ratingAccent = Color(0xFFF59E0B);
  static const Color _secondaryFixed = Color(0xFFDDE1FF);
  static const Color _onSecondaryFixed = Color(0xFF001355);
  static const Color _outline = Color(0xFF747687);

  int _selectedTab = 0;
  static const List<String> _tabs = <String>['About', 'Curriculum', 'Reviews'];

  static const List<_Lesson> _lessons = <_Lesson>[
    _Lesson(title: 'Introduction to Advanced UI', duration: '12:45 mins', locked: false),
    _Lesson(title: 'Designing for Scalability', duration: '24:20 mins', locked: true),
    _Lesson(title: 'Advanced Prototyping II', duration: '18:15 mins', locked: true),
  ];

  Future<void> _handleEnroll() async {
    // TODO: Replace with your enrollment / Stripe / Razorpay / Firestore call.
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: _surface,
      bottomNavigationBar: _buildStickyFooter(),
      body: SingleChildScrollView(
        padding: EdgeInsets.zero,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildHero(context),
            Transform.translate(
              offset: const Offset(0, -24),
              child: Container(
                width: double.infinity,
                decoration: const BoxDecoration(
                  color: _surface,
                  borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
                  boxShadow: [
                    BoxShadow(color: Color(0x0A000000), blurRadius: 20, offset: Offset(0, -8)),
                  ],
                ),
                padding: const EdgeInsets.fromLTRB(20, 24, 20, 24),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _buildCategoryPill(),
                    const SizedBox(height: 8),
                    const Text(
                      'UI/UX Advanced',
                      style: TextStyle(
                        fontSize: 28,
                        fontWeight: FontWeight.w700,
                        color: _textPrimary,
                        height: 1.2,
                        letterSpacing: -0.56,
                      ),
                    ),
                    const SizedBox(height: 16),
                    _buildRatingMentorRow(),
                    const SizedBox(height: 24),
                    _buildTabs(),
                    const SizedBox(height: 24),
                    _buildTabContent(),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHero(BuildContext context) {
    return AspectRatio(
      aspectRatio: 16 / 9,
      child: Stack(
        fit: StackFit.expand,
        children: [
          Container(
            decoration: const BoxDecoration(
              gradient: LinearGradient(
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
                colors: [Color(0xFF4568F3), Color(0xFF0042DE)],
              ),
            ),
          ),
          Positioned(
            top: -40,
            left: -20,
            child: Container(
              width: 200,
              height: 200,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.white.withOpacity(0.12),
              ),
            ),
          ),
          SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  _glassButton(
                    icon: Icons.arrow_back,
                    onTap: () => Navigator.maybePop(context),
                  ),
                  _glassButton(
                    icon: Icons.share_outlined,
                    onTap: () {
                      // TODO: Replace with your share sheet logic.
                    },
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _glassButton({required IconData icon, required VoidCallback onTap}) {
    return Material(
      color: Colors.white.withOpacity(0.85),
      borderRadius: BorderRadius.circular(12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(10),
          child: Icon(icon, color: _textPrimary, size: 20),
        ),
      ),
    );
  }

  Widget _buildCategoryPill() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
      decoration: BoxDecoration(
        color: _secondaryFixed,
        borderRadius: BorderRadius.circular(999),
      ),
      child: const Text(
        'DESIGN',
        style: TextStyle(
          fontSize: 10,
          fontWeight: FontWeight.w700,
          letterSpacing: 1.6,
          color: _onSecondaryFixed,
        ),
      ),
    );
  }

  Widget _buildRatingMentorRow() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Row(
          mainAxisSize: MainAxisSize.min,
          children: const [
            Icon(Icons.star_rounded, color: _ratingAccent, size: 22),
            SizedBox(width: 4),
            Text('4.8',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: _textPrimary)),
            SizedBox(width: 4),
            Text('(1.2k reviews)',
                style: TextStyle(fontSize: 14, color: _textMuted)),
          ],
        ),
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
          decoration: BoxDecoration(
            color: _surfaceContainerLow,
            borderRadius: BorderRadius.circular(999),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 32,
                height: 32,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  gradient: const LinearGradient(
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                    colors: [Color(0xFFB8C4FF), Color(0xFF4568F3)],
                  ),
                  border: Border.all(color: _surface, width: 2),
                ),
                alignment: Alignment.center,
                child: const Text(
                  'S',
                  style: TextStyle(
                    color: _onSecondaryFixed,
                    fontWeight: FontWeight.w700,
                    fontSize: 14,
                  ),
                ),
              ),
              const SizedBox(width: 8),
              const Text(
                'Sarah',
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: _textPrimary),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildTabs() {
    return Container(
      decoration: const BoxDecoration(
        border: Border(bottom: BorderSide(color: _surfaceContainer, width: 1)),
      ),
      child: Row(
        children: List<Widget>.generate(_tabs.length, (int i) {
          final bool selected = i == _selectedTab;
          return Expanded(
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: () => setState(() => _selectedTab = i),
              child: Padding(
                padding: const EdgeInsets.only(bottom: 12),
                child: Column(
                  children: [
                    Text(
                      _tabs[i],
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.w500,
                        color: selected ? _primary : _textMuted,
                      ),
                    ),
                    const SizedBox(height: 12),
                    Container(
                      height: 2,
                      color: selected ? _primary : Colors.transparent,
                    ),
                  ],
                ),
              ),
            ),
          );
        }),
      ),
    );
  }

  Widget _buildTabContent() {
    switch (_selectedTab) {
      case 1:
        return _buildCurriculumOnly();
      case 2:
        return _buildReviewsPlaceholder();
      case 0:
      default:
        return _buildAbout();
    }
  }

  Widget _buildAbout() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Course Description',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: _textPrimary),
        ),
        const SizedBox(height: 12),
        const Text(
          "Take your design skills to the next level with this comprehensive UI/UX advanced masterclass. You will learn professional workflows, complex prototyping, and user-centric design strategies used by top-tier product teams globally. We'll dive deep into Figma's latest features and real-world case studies.",
          style: TextStyle(fontSize: 14, color: _textBody, height: 1.6),
        ),
        const SizedBox(height: 32),
        _buildCurriculumHeader(),
        const SizedBox(height: 12),
        ..._lessons.map(_buildLessonCard),
      ],
    );
  }

  Widget _buildCurriculumOnly() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildCurriculumHeader(),
        const SizedBox(height: 12),
        ..._lessons.map(_buildLessonCard),
      ],
    );
  }

  Widget _buildReviewsPlaceholder() {
    return const SizedBox(
      width: double.infinity,
      child: Padding(
        padding: EdgeInsets.symmetric(vertical: 32),
        child: Text(
          'Reviews coming soon',
          textAlign: TextAlign.center,
          style: TextStyle(fontSize: 14, color: _textMuted),
        ),
      ),
    );
  }

  Widget _buildCurriculumHeader() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: const [
        Text('Curriculum',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: _textPrimary)),
        Text('12 Lessons',
            style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: _primary)),
      ],
    );
  }

  Widget _buildLessonCard(_Lesson lesson) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Container(
        padding: const EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: _surfaceCard,
          borderRadius: BorderRadius.circular(12),
          boxShadow: const [
            BoxShadow(color: Color(0x0F000000), blurRadius: 16, offset: Offset(0, 4)),
          ],
        ),
        child: Row(
          children: [
            Container(
              width: 48,
              height: 48,
              decoration: BoxDecoration(
                color: _primaryContainer.withOpacity(0.1),
                borderRadius: BorderRadius.circular(10),
              ),
              child: const Icon(Icons.play_circle, color: _primary, size: 26),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    lesson.title,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                      color: _textPrimary,
                    ),
                  ),
                  const SizedBox(height: 2),
                  Text(
                    lesson.duration,
                    style: const TextStyle(fontSize: 12, color: _textMuted),
                  ),
                ],
              ),
            ),
            Icon(
              lesson.locked ? Icons.lock_outline : Icons.chevron_right,
              color: _outline,
              size: 22,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStickyFooter() {
    return Container(
      decoration: const BoxDecoration(
        color: _surfaceCard,
        boxShadow: [
          BoxShadow(color: Color(0x14000000), blurRadius: 24, offset: Offset(0, -4)),
        ],
      ),
      padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
      child: SafeArea(
        top: false,
        child: Row(
          children: [
            Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: const [
                Text(
                  'TOTAL PRICE',
                  style: TextStyle(
                    fontSize: 12,
                    color: _textMuted,
                    letterSpacing: 0.4,
                    fontWeight: FontWeight.w600,
                  ),
                ),
                Text(
                  r'$49',
                  style: TextStyle(
                    fontSize: 28,
                    fontWeight: FontWeight.w700,
                    color: _primary,
                    height: 1.1,
                  ),
                ),
              ],
            ),
            const SizedBox(width: 20),
            Expanded(
              child: SizedBox(
                height: 52,
                child: ElevatedButton(
                  onPressed: _handleEnroll,
                  style: ElevatedButton.styleFrom(
                    backgroundColor: _primaryContainer,
                    foregroundColor: Colors.white,
                    elevation: 4,
                    shadowColor: _primaryContainer.withOpacity(0.3),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: const [
                      Text(
                        'Enroll Now',
                        style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
                      ),
                      SizedBox(width: 12),
                      Icon(Icons.arrow_forward, size: 20),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _Lesson {
  final String title;
  final String duration;
  final bool locked;
  const _Lesson({
    required this.title,
    required this.duration,
    required this.locked,
  });
}

How to use this widget

  1. 1Copy the Dart code above
  2. 2Create a new file — course_detail_page.dart
  3. 3Paste the code and add to your route. Replace the TODO comments with your enrollment / payment / share-sheet calls.

Related components

E-commerce

Course Detail Screen

E-learning home with promo banner, mentors, category chips and course cards

View component →
Auth & Onboarding

Sign Up with Role Selector

Registration screen with role selection grid — Owner, Manager, or Employee

View component →
Auth & Onboarding

Login Screen

Clean email + password login with form validation and loading state

View component →