Flutter Tutorial

Flutter Beginner Lesson 25 of 135 3 min read

Card, ListTile, Chip and Divider in Flutter

Display content in Flutter with Card, ListTile, Chip, Divider, Badge and ExpansionTile, and combine them into polished list items.

On this page

These small components are what most screens are made of. Knowing them saves you from building everything out of containers.

Card

A Card is a surface with rounded corners that groups related content.

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 320,
            child: Card(
              clipBehavior: Clip.antiAlias,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(height: 120, color: Colors.teal.shade200),
                  const ListTile(
                    title: Text('Mountain view room'),
                    subtitle: Text('2 guests, breakfast included'),
                    trailing: Text('Rs. 4,500'),
                  ),
                  Padding(
                    padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        TextButton(onPressed: () {}, child: const Text('Details')),
                        const SizedBox(width: 8),
                        FilledButton(onPressed: () {}, child: const Text('Book')),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    ),
  );
}
VariantLook
CardSlight elevation
Card.filledFilled with a tinted colour, no shadow
Card.outlinedA border, no shadow

A card has no padding of its own. Add a Padding inside. To make the whole card tappable with a ripple, wrap its content in an InkWell.

ListTile

ListTile(
  leading: const CircleAvatar(child: Icon(Icons.person)),
  title: const Text('Bimal Thapa'),
  subtitle: const Text('Online 5 minutes ago'),
  trailing: const Icon(Icons.chevron_right),
  onTap: () {},
)

Variants that include a control:

SwitchListTile(
  title: const Text('Notifications'),
  subtitle: const Text('Order updates and offers'),
  value: _notifications,
  onChanged: (v) => setState(() => _notifications = v),
)

CheckboxListTile(
  title: const Text('Remember me'),
  value: _remember,
  onChanged: (v) => setState(() => _remember = v ?? false),
)

Tapping anywhere on the row toggles the control, which is easier to hit than the small control alone.

ExpansionTile

A row that opens to reveal more.

const ExpansionTile(
  leading: Icon(Icons.help_outline),
  title: Text('How do I track my order?'),
  children: [
    Padding(
      padding: EdgeInsets.fromLTRB(16, 0, 16, 16),
      child: Text('Open Orders, choose the order and tap Track.'),
    ),
  ],
)

Chips

Compact elements for tags, filters and choices.

Wrap(
  spacing: 8,
  children: [
    const Chip(avatar: Icon(Icons.tag, size: 18), label: Text('Flutter')),
    FilterChip(
      label: const Text('In stock'),
      selected: _inStock,
      onSelected: (v) => setState(() => _inStock = v),
    ),
    ChoiceChip(
      label: const Text('Large'),
      selected: _size == 'L',
      onSelected: (_) => setState(() => _size = 'L'),
    ),
    ActionChip(
      avatar: const Icon(Icons.directions, size: 18),
      label: const Text('Directions'),
      onPressed: () {},
    ),
    InputChip(label: const Text('asha@example.com'), onDeleted: () {}),
  ],
)
ChipUse
ChipA read-only label
FilterChipToggle a filter on or off. Several can be on
ChoiceChipPick one from a set
ActionChipTrigger an action
InputChipAn item the user entered, removable

Divider

const Divider()                                // a line with space above and below
const Divider(height: 1, indent: 72)           // a tight line, indented past the avatar
const VerticalDivider(width: 24)               // in a Row with a fixed height

Badge

const Badge(label: Text('5'), child: Icon(Icons.shopping_cart))
const Badge(child: Icon(Icons.notifications)) // a plain dot

Progress indicators

const CircularProgressIndicator()              // unknown duration
const LinearProgressIndicator(value: 0.6)      // 60% done

Putting it together: an order row

class OrderTile extends StatelessWidget {
  const OrderTile({super.key, required this.id, required this.status, required this.total});

  final String id;
  final String status;
  final String total;

  @override
  Widget build(BuildContext context) {
    final delivered = status == 'Delivered';
    return Card(
      child: ListTile(
        leading: Icon(delivered ? Icons.check_circle : Icons.local_shipping),
        title: Text('Order $id'),
        subtitle: Text(status),
        trailing: Text(total, style: Theme.of(context).textTheme.titleMedium),
        onTap: () {},
      ),
    );
  }
}

Try it yourself

Build a restaurant card: an image area at the top, a name and cuisine, a row of chips for tags such as “Vegetarian” and “Free delivery”, and a rating badge in the corner.

Practise in the playground Updated