Component reference
Everything in kite_ui. All of it is on the
Components screen
in the running app, live and stateful.
One import gets you the lot. Features never import anything below it — see the one rule.
import '../../kite_ui/kite_ui.dart';
Tokens
KiteColors.of(context)
Theme colours as an extension type. See Theming for the full token list.
final c = KiteColors.of(context);
c.background c.card c.border c.primary c.muted
c.mutedForeground c.accent c.destructive c.ring
KiteText.of(context)
Type scale. h1–h4, p, lead, large, small, muted.
Text('Revenue', style: KiteText.of(context).h3)
KiteSpace · KiteRadius · KiteBreak
Spacing scale, corner radii, and responsive predicates.
const EdgeInsets.all(KiteSpace.xl) // 24
borderRadius: KiteRadius.allLg // 12
if (KiteBreak.isDesktop(context)) … // ≥ 1024
KiteFormat
Money and counts. Money is always two decimals with a thousands separator — $699.0 in a column of currency reads as a bug, because it is one.
KiteFormat.money(1264.55) // $1,264.55
KiteFormat.compactMoney(284120) // $284K
KiteFormat.count(3412) // 3,412
KiteFormat.money(null) // —
Layout and surfaces
KiteCard
The standard surface. Optional title and trailing widget.
KiteCard(
title: 'Revenue',
trailing: const KiteBadge('Last 60 days', tone: KiteTone.info),
child: SizedBox(height: 250, child: KiteLineChart(spots: spots)),
)
KiteStat
A stat tile: label, value, and a delta whose direction is encoded in colour as well as sign.
KiteStat(
label: 'Revenue',
value: r'$284,120',
delta: '+12.4%',
deltaTone: KiteTone.success,
)
KiteStatGrid
Lays tiles out at fixed height, flexible width, dropping a
column when they would fall below minTileWidth.
Use this rather than GridView.count with a
childAspectRatio. Aspect ratio ties height to width, so on
a wide screen the tiles grow taller while every other card keeps its
content height — an inflated row above everything else.
KiteStatGrid(
height: 200, // constant at every viewport width
minTileWidth: 220,
children: [ /* tiles */ ],
)
KiteSeparator
const KiteSeparator() // horizontal
const KiteSeparator(vertical: true)
Data display
KiteDataTable
A themed trina_grid. Columns share the available width, currency
columns keep numeric sort, and an explicit trailing action column opens a row.
KiteDataTable(
paginate: false, // false when you page server-side
onRowTap: (i) => context.go('/orders/${rows[i]['id']}'),
columns: [
TrinaColumn(
title: 'Total',
field: 'total',
readOnly: true,
textAlign: TrinaColumnTextAlign.end,
type: TrinaColumnType.currency(symbol: r'$', decimalDigits: 2),
),
],
rows: [
TrinaRow(cells: {'total': TrinaCell(value: 1264.55)}),
],
)
Row opening is an explicit action column, not double-tap. Double-tap
is undiscoverable, and on desktop trina_grid detects it with a
manual timing window that is easy to miss. Double-tap still works for
anyone who tries it.
KiteBadge KiteTone
Status. Tone is semantic and separate from the brand accent.
const KiteBadge('Paid', tone: KiteTone.success)
const KiteBadge('Pending', tone: KiteTone.warning)
const KiteBadge('Cancelled', tone: KiteTone.danger)
const KiteBadge('Refunded', tone: KiteTone.info)
const KiteBadge('Draft') // neutral
KiteAvatar
Falls back to initials, which is the common case — admin tables rarely have photographs.
const KiteAvatar(name: 'Ada Lovelace', size: 32)
const KiteAvatar(name: 'Ada Lovelace', src: 'https://…')
KiteMeter
A labelled progress bar with the value in tabular figures.
const KiteMeter(label: 'Storage used', value: 0.72, trailing: '72 of 100 GB')
Charts
KiteLineChart
Faint horizontal grid, area fill, no axis furniture competing with the data. Tooltips take a formatter.
KiteLineChart(
spots: [for (var i = 0; i < values.length; i++) (i.toDouble(), values[i])],
valueLabel: (v) => KiteFormat.money(v),
)
Always pass valueLabel. Without it a hover prints the raw double — 74.81973839205.
KiteSparkline
Trend without axes. Leave height null inside an Expanded and it fills its slot.
Expanded(child: KiteSparkline(values: spark, color: accent))
KiteDonut KiteSlice
Total in the hole, labels in the legend — labels on the arcs collide once a slice drops below about 5%.
KiteDonut(
centerLabel: 'orders',
centerValue: KiteFormat.count(3412),
slices: const [
KiteSlice(label: 'Paid', value: 1412, color: Color(0xFF0C6B62)),
KiteSlice(label: 'Pending', value: 604, color: Color(0xFFC08A19)),
],
)
KiteBarRow
Ranked categories — answers "which is biggest" faster than a pie.
KiteBarRow(label: 'Direct', value: 20460, max: 20460, display: '42%')
Actions
KiteButton 6 variants
KiteButton(onPressed: save, child: const Text('Save'))
KiteButton.secondary(…) KiteButton.outline(…)
KiteButton.ghost(…) KiteButton.destructive(…)
KiteButton(
leading: const Icon(Icons.download, size: 16),
expands: true, // fill the parent's width
enabled: !busy, // dims as well as disabling
onPressed: export,
child: const Text('Export'),
)
KiteMenu KiteMenuItem
A dropdown anchored to its trigger. Closes itself before running a callback, so a menu item that navigates cannot leave a popover over the next screen.
KiteMenu(
width: 248,
header: const Text('Signed in as Ada'),
items: [
KiteMenuItem(label: 'Profile', icon: Icons.person_outline, onPressed: …),
KiteMenuItem(label: 'Theme', icon: Icons.palette_outlined, trailing: 'Slate', onPressed: …),
const KiteMenuItem.separator(),
KiteMenuItem(label: 'Sign out', icon: Icons.logout, destructive: true, onPressed: …),
],
trigger: (context, open) => IconButton(icon: …, onPressed: open),
)
KiteTooltip
KiteTooltip(message: 'Notifications', child: IconButton(…))
Overlays and feedback
KiteToast
A control that says what it will do should be followed by a message saying it happened.
KiteToast.show(
context,
title: 'Order deleted',
description: 'You can restore it from the archive for 30 days.',
tone: KiteTone.danger,
)
kiteConfirm
Destructive confirmation. Returns true only on an explicit confirm — dismissing resolves to false, never to yes.
final ok = await kiteConfirm(
context,
title: 'Delete #10004?',
message: 'This removes the record permanently and cannot be undone.',
);
if (!ok || !context.mounted) return;
kiteSheet
Right-hand detail drawer — inspect a row without losing your place in the table.
kiteSheet<void>(
context,
title: '#10004',
description: 'Placed 24 August by Ada Lovelace.',
body: Column(children: […]),
actions: [KiteButton.outline(…), KiteButton.destructive(…)],
)
KiteAlert
Inline notice for state the page has to explain.
const KiteAlert(
title: 'Low stock on 3 products',
description: 'Reorder before Friday to avoid backorders.',
tone: KiteTone.warning,
)
Forms
KiteField
The labelled wrapper. Labels sit outside the control so they never move; errors say what went wrong and how to fix it.
KiteField(
label: 'Contact email',
hint: 'Used for stock alerts only.',
error: errors['email'],
child: KiteInput(controller: email, keyboardType: TextInputType.emailAddress),
)
KiteInput · KiteTextarea
KiteInput(
controller: search,
placeholder: 'Search orders…',
leading: Icon(Icons.search, size: 16, color: c.mutedForeground),
onChanged: (v) => …,
)
KiteTextarea(controller: notes, placeholder: 'Optional')
KiteSelect<T>
KiteSelect<String>(
options: const ['Paid', 'Pending', 'Refunded'],
labelOf: (v) => v,
value: status,
placeholder: 'Choose one',
onChanged: (v) => setState(() => status = v),
)
KiteCheckbox · KiteSwitch
Both take a label and an optional sublabel, so the control explains itself.
KiteSwitch(
value: notify,
label: 'Email me when stock runs low',
sublabel: 'At most one message a day.',
onChanged: (v) => setState(() => notify = v),
)
KiteSlider · KiteDatePicker
KiteSlider(value: discount, min: 0, max: 100, onChanged: …)
KiteDatePicker(
selected: shipDate,
placeholder: 'Pick a date',
onChanged: (d) => setState(() => shipDate = d),
)
States
LoadingState · EmptyState · ErrorState
From shared/widgets/states.dart. Free templates habitually stop at the happy path.
async.when(
loading: () => const LoadingState(),
error: (e, _) => ErrorState(message: '$e', onRetry: () => ref.invalidate(p)),
data: (result) => result.rows.isEmpty
? EmptyState(
title: 'Nothing matches those filters',
message: 'Try clearing the search or choosing a different status.',
actionLabel: 'Reset filters',
onAction: reset,
)
: …,
)