When you’re new to Flutter, you might look for a simple BulletList
widget and be surprised to find there isn’t one built-in. But don’t worry! Flutter’s compositional nature makes it incredibly easy to build your own, giving you full control over the style.
This guide will show you the standard, flexible way to create a bulleted list using fundamental layout widgets.
The Basic Approach: Combining Row
and Text
The core idea is to create a custom widget for a single list item. This item is just a Row
containing two things: the bullet point and the text.
Here’s how to structure a single bulleted item:
Row(
crossAxisAlignment: CrossAxisAlignment.start, // Aligns items to the start of the row
children: [
Text(
'• ', // The bullet point character
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(width: 8), // Adds some space between the bullet and the text
Expanded(
child: Text(
'This is the text for the bullet point. It will wrap automatically if it is too long to fit on a single line.',
style: TextStyle(fontSize: 16),
),
),
],
)
DartKey Widgets Used:
Row
: The main container that arranges its children horizontally.crossAxisAlignment.start
: This is important! It ensures that if your text wraps to multiple lines, the bullet point stays aligned with the top of the text.Text('• ')
: A simpleText
widget for the bullet character. You can customize itsstyle
.SizedBox(width: 8)
: Provides consistent spacing.Expanded
: This is crucial. It tells theText
widget to take up the remaining available horizontal space, which allows it to wrap properly without causing a pixel overflow error.
Building the Full List with Column
Now that you have the pattern for a single item, you can stack them vertically using a Column
to create the full list.
Complete Code Example
Here’s a full, runnable example of a screen displaying a bulleted list.
import 'package:flutter/material.dart';
class BulletListScreen extends StatelessWidget {
const BulletListScreen({super.key});
final List<String> features = const [
'Build beautiful UIs from a single codebase.',
'Fast development with Hot Reload.',
'Excellent performance on both iOS and Android.',
'Access to native features and SDKs.',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Features'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: features.map((feature) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0), // Space between list items
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('• ', style: TextStyle(fontSize: 18)),
const SizedBox(width: 8),
Expanded(child: Text(feature, style: const TextStyle(fontSize: 16))),
],
),
);
}).toList(),
),
),
);
}
}
DartIn this example, we map over a list of strings (features
) and create our custom Row
for each one. Using ListView.builder
instead of Column
is recommended for very long or dynamic lists for better performance.
Customizing Your Bullet Points
The beauty of this approach is its flexibility. Don’t want a standard bullet? Swap it out!
Using an Icon
Replace the Text('• ')
with an Icon
widget for a more modern look.
// Instead of Text('• ')...
Icon(
Icons.check_circle,
color: Colors.green,
size: 20,
),
DartUsing a Custom Shape
Create a custom colored circle using a Container
with BoxDecoration
.
// Instead of Text('• ')...
Container(
margin: const EdgeInsets.only(top: 5.0), // To align with the text
height: 8,
width: 8,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
DartBy building your own list items, you can match any design requirement perfectly. This compositional approach is at the heart of what makes Flutter’s UI framework so powerful.