Kite docs

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. h1h4, 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,
        )
      : …,
)