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')),
],
),
),
],
),
),
),
),
),
),
);
}
| Variant | Look |
|---|---|
Card | Slight elevation |
Card.filled | Filled with a tinted colour, no shadow |
Card.outlined | A 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: () {}),
],
)
| Chip | Use |
|---|---|
Chip | A read-only label |
FilterChip | Toggle a filter on or off. Several can be on |
ChoiceChip | Pick one from a set |
ActionChip | Trigger an action |
InputChip | An 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.