Typography
Headings, paragraphs, quotes and lists in the theme's type.
import 'package:flutter/widgets.dart';
import 'ui/typography.dart';
class TypographyDemo extends StatelessWidget {
const TypographyDemo({super.key});
@override
Widget build(BuildContext context) => const SizedBox(
width: 520,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
H2('The market at Gikomba'),
SizedBox(height: 12),
Lead('Before sunrise the stalls are already up, and the first buyers are haggling.'),
SizedBox(height: 16),
P(
'Sellers arrive with bales from the port, sorting by what will sell first. By noon the best pieces are gone.',
),
SizedBox(height: 16),
Blockquote('"Come early, or come back tomorrow."'),
],
),
);
}
Add it
In your Flutter project:
ashlar add typography
It writes theme.dart, palette.dart and typography.dart into lib/ui, keeping any you already have.
Examples
Other ways to use it, each running live, with its code.
A table
Rows of text in columns.
// A table: Rows of text in columns.
import 'package:flutter/widgets.dart';
import 'ui/typography.dart';
class TypographyTableExample extends StatelessWidget {
const TypographyTableExample({super.key});
@override
Widget build(BuildContext context) => const SizedBox(
width: 420,
child: TypographyTable(
headings: ['County', 'Capital'],
rows: [
['Nairobi', 'Nairobi'],
['Kisumu', 'Kisumu'],
['Nakuru', 'Nakuru'],
],
),
);
}
API
Read from the source above: each parameter with its type, its default, and what it is for.
H1class
The page's title: 36, bold.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
H2class
A section: 30, semibold.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
H3class
A part of a section: 24, semibold.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
H4class
A small heading: 20, semibold.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
Pclass
A paragraph of reading text: 16, with room between lines.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
Leadclass
The opening paragraph, larger and quieter: 20, muted.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
Largeclass
Text that stands out a little: 18, semibold.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
Smallclass
A small note: 14, medium.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
Mutedclass
Text that recedes: 14, muted.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
Blockquoteclass
A quotation, set in from a line at its left.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
InlineCodeclass
A word of code inside a sentence.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
textpositional |
String |
The words. |
BulletListclass
A bulleted list.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
itemspositional |
List<String> |
The points, in order. |
TypographyTableclass
A small table in running text: headings and rows of plain words, lined and banded.
| Parameter | Type | Default | What it is for |
|---|---|---|---|
headingsrequired |
List<String> |
The column headings. | |
rowsrequired |
List<List<String>> |
The rows, each a list of cells. |
Source
What lands in your app. It is yours from then on: change it as you would your own code, and ashlar add
will not write over it.
lib/ui/typography.dart 307 lines
// Ashlar UI: Typography. Copied into your app by `ashlar add typography`; it is yours to
// change.
import 'package:flutter/widgets.dart';
import 'theme.dart';
TextStyle _style(
BuildContext context,
TextStyle Function(AshlarText t) size, {
FontWeight? weight,
double? spacing,
Color? color,
}) {
final t = AshlarTheme.of(context);
return size(t.text).copyWith(
color: color ?? t.colors.foreground,
fontWeight: weight,
letterSpacing: spacing,
);
}
Widget _heading(BuildContext context, String text, TextStyle style) =>
Semantics(header: true, child: Text(text, style: style));
/// The page's title: 36, bold.
class H1 extends StatelessWidget {
const H1(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => _heading(
context,
text,
_style(
context,
(t) => t.xxl,
weight: FontWeight.w800,
spacing: -.9,
).copyWith(fontSize: 36, height: 40 / 36),
);
}
/// A section: 30, semibold.
class H2 extends StatelessWidget {
const H2(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => _heading(
context,
text,
_style(
context,
(t) => t.xxl,
weight: FontWeight.w600,
spacing: -.75,
).copyWith(fontSize: 30, height: 36 / 30),
);
}
/// A part of a section: 24, semibold.
class H3 extends StatelessWidget {
const H3(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => _heading(
context,
text,
_style(context, (t) => t.xxl, weight: FontWeight.w600, spacing: -.6),
);
}
/// A small heading: 20, semibold.
class H4 extends StatelessWidget {
const H4(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => _heading(
context,
text,
_style(context, (t) => t.xl, weight: FontWeight.w600, spacing: -.5),
);
}
/// A paragraph of reading text: 16, with room between lines.
class P extends StatelessWidget {
const P(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => Text(
text,
style: _style(context, (t) => t.base).copyWith(height: 28 / 16),
);
}
/// The opening paragraph, larger and quieter: 20, muted.
class Lead extends StatelessWidget {
const Lead(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => Text(
text,
style: _style(
context,
(t) => t.xl,
color: AshlarTheme.of(context).colors.mutedForeground,
),
);
}
/// Text that stands out a little: 18, semibold.
class Large extends StatelessWidget {
const Large(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) =>
Text(text, style: _style(context, (t) => t.lg, weight: FontWeight.w600));
}
/// A small note: 14, medium.
class Small extends StatelessWidget {
const Small(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) =>
Text(text, style: _style(context, (t) => t.sm, weight: FontWeight.w500));
}
/// Text that recedes: 14, muted.
class Muted extends StatelessWidget {
const Muted(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => Text(
text,
style: _style(
context,
(t) => t.sm,
color: AshlarTheme.of(context).colors.mutedForeground,
),
);
}
/// A quotation, set in from a line at its left.
class Blockquote extends StatelessWidget {
const Blockquote(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsetsDirectional.only(start: 24),
decoration: BoxDecoration(
border: BorderDirectional(
start: BorderSide(
color: AshlarTheme.of(context).colors.border,
width: 2,
),
),
),
child: Text(
text,
style: _style(
context,
(t) => t.base,
).copyWith(fontStyle: FontStyle.italic, height: 28 / 16),
),
);
}
/// A word of code inside a sentence.
class InlineCode extends StatelessWidget {
const InlineCode(this.text, {super.key});
/// The words.
final String text;
@override
Widget build(BuildContext context) {
final t = AshlarTheme.of(context);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: t.colors.muted,
borderRadius: BorderRadius.circular(4),
),
child: Text(
text,
style: t.text.sm.copyWith(
fontFamily: 'monospace',
fontWeight: FontWeight.w600,
color: t.colors.foreground,
),
),
);
}
}
/// A bulleted list.
class BulletList extends StatelessWidget {
const BulletList(this.items, {super.key});
/// The points, in order.
final List<String> items;
@override
Widget build(BuildContext context) {
final style = _style(context, (t) => t.base);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final item in items)
Padding(
padding: const EdgeInsetsDirectional.only(start: 24, bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExcludeSemantics(child: Text('• ', style: style)),
Expanded(child: Text(item, style: style)),
],
),
),
],
);
}
}
/// A small table in running text: headings and rows of plain words, lined and banded.
class TypographyTable extends StatelessWidget {
const TypographyTable({
super.key,
required this.headings,
required this.rows,
});
/// The column headings.
final List<String> headings;
/// The rows, each a list of cells.
final List<List<String>> rows;
@override
Widget build(BuildContext context) {
final t = AshlarTheme.of(context);
final c = t.colors;
final line = BorderSide(color: c.border);
TableRow row(
List<String> cells, {
bool head = false,
bool band = false,
}) => TableRow(
decoration: BoxDecoration(color: band ? c.muted : null),
children: [
for (final cell in cells)
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
border: Border(left: line, right: line, top: line, bottom: line),
),
child: Semantics(
header: head,
child: Text(
cell,
style: t.text.base.copyWith(
color: c.foreground,
fontWeight: head ? FontWeight.w700 : null,
),
),
),
),
],
);
return Table(
children: [
row(headings, head: true),
for (final (i, r) in rows.indexed) row(r, band: i.isOdd),
],
);
}
}