Dart Tutorial

Flutter Lesson 24 of 83 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 by Santosh Adhikari