Install
Unzip the download and copy the tarn_ui folder into your repository — packages/tarn_ui is a good home. Then depend on it by path (or push it to a private git repo and depend on that):
dependencies:
flutter:
sdk: flutter
tarn_ui:
path: packages/tarn_ui # or a private git URLRun flutter pub get and import package:tarn_ui/tarn_ui.dart. The IBM Plex fonts ship inside the package, so there is nothing else to declare.
Theme
TarnTheme returns an ordinary ThemeData with a matching ColorScheme, so Material widgets you already use pick up Tarn’s colors. The palette itself lives in the TarnColors theme extension.
MaterialApp(
theme: TarnTheme.light(),
darkTheme: TarnTheme.dark(),
themeMode: ThemeMode.system,
home: const HomePage(),
);
// Your brand color instead of Tarn teal:
TarnTheme.fromSeed(const Color(0xFF3D5AFE));
TarnTheme.fromSeed(const Color(0xFF3D5AFE), brightness: Brightness.dark);
// Full control over the palette:
final colors = TarnColors.light.copyWith(
canvas: const Color(0xFFF7F7F5),
sidebar: const Color(0xFF101418),
);
TarnTheme.build(colors, Brightness.light);fromSeed adjusts the seed’s lightness for the chosen brightness, then picks dark or white text for it by luminance, so labels on buttons stay readable whatever color you pass.
App shell
TarnAppShell renders one set of destinations three ways: a bottom bar with a “More” sheet below 640 px, a rail from 640 px, and a sidebar with sections, workspace header and user card from 1100 px. The top bar shows a breadcrumb and, on wide windows, a search field.
TarnAppShell(
workspaceName: 'Acme',
destinations: const [
TarnDestination(id: 'overview', label: 'Overview',
icon: Icons.space_dashboard_outlined, section: 'Workspace'),
TarnDestination(id: 'invoices', label: 'Invoices',
icon: Icons.receipt_long_outlined, badge: '3'),
TarnDestination(id: 'settings', label: 'Settings',
icon: Icons.settings_outlined, section: 'Account'),
],
selectedId: current,
onSelect: (id) => setState(() => current = id),
user: const TarnShellUser(name: 'Mara Ellison', email: 'mara@acme.com'),
body: pages[current]!,
);Widgets
| Actions | TarnButton (5 variants, 3 sizes, loading), TarnIconButton, TarnLink, TarnMenuButton |
|---|---|
| Input | TarnTextField, TarnSearchField, TarnCodeField, TarnSelect, TarnCheckbox, TarnSwitch |
| Selection | TarnSegmented, TarnTabs, TarnFilterChips |
| Display | TarnCard, TarnBadge, TarnMetricTile, TarnAvatar, TarnAvatarStack, TarnListTile, TarnProgressBar, TarnEmptyState |
| Feedback | showTarnToast, showTarnConfirm, showTarnSheet, TarnDialog, TarnCallout, TarnSkeleton |
| Data | TarnDataTable with sorting, selection and responsive scrolling |
| Charts | TarnLineChart, TarnBarChart, TarnDonutChart, TarnSparkline, TarnChartLegend |
| Layout | TarnAppShell, TarnPage, TarnPageHeader, TarnResponsiveGrid, TarnAdaptiveRow, TarnAuthLayout, TarnLogo |
TarnButton(label: 'New invoice', icon: Icons.add, onPressed: create);
TarnButton(label: 'Export', variant: TarnButtonVariant.outline, onPressed: export);
TarnBadge(label: 'Overdue', tone: TarnTone.negative, dot: true);
TarnMetricTile(label: 'Collected', value: r'$48,210', delta: 12.4);
showTarnToast(context, 'Reminder sent', tone: TarnTone.positive);
final ok = await showTarnConfirm(context,
title: 'Void invoice?', message: 'This can’t be undone.',
confirmLabel: 'Void', destructive: true);Data tables
Columns describe how to render and sort a row. Header taps toggle sorting; with selectable on, a header checkbox selects every row. When the table is narrower than its columns’ minimum widths it scrolls horizontally instead of squeezing text.
TarnDataTable<Customer>(
rows: customers,
selectable: true,
selected: selected,
onSelectionChanged: (s) => setState(() => selected = s),
columns: [
TarnColumn(label: 'Customer', flex: 2,
cell: (context, c) => Text(c.name), sortKey: (c) => c.name),
TarnColumn(label: 'Balance', numeric: true,
cell: (context, c) => Text(TarnFormat.money(c.balance)),
sortKey: (c) => c.balance),
],
);Charts
Charts are painted with CustomPainter and read colors from the theme. Axis maximums are rounded to readable steps, the line chart shows a tooltip on hover or drag, and bars can be grouped or stacked. Give each chart a semanticLabel for screen readers.
TarnLineChart(
labels: const ['Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov'],
series: const [
TarnSeries(name: 'Billed', values: [31, 34, 33, 38, 41, 44]),
TarnSeries(name: 'Collected', values: [27, 30, 31, 33, 37, 40]),
],
valueFormat: (v) => TarnFormat.money(v * 1000),
semanticLabel: 'Billed and collected, last six months',
);Screens
Nine full screens are exported: sign in, sign up, verify code, overview, customers, invoices, inbox, reports and settings. They run on demo data so you can see every state; each exposes callbacks for the parts you’ll connect, and is a readable starting point to copy into your app and adapt.
TarnSignInScreen(
productName: 'Acme',
onSignIn: (email, password) async {
await auth.signIn(email, password); // throw to show the message in the form
},
onForgotPassword: () => context.go('/reset'),
);Tokens
context.tarn— the activeTarnColors(canvas, surface, ink, accent, tones, sidebar, chart series…)TarnType— display, title, heading, body, small, caption, label and mono styles, with tabular figures for numbersTarnSpace.x1 … x10— a 4-point spacing scale;TarnRadius.sm / md / lg / pillTarnBreakpoints.compact(640) and.expanded(1100)TarnFormat— money, number, compact (48.2k), dates
Showcase & tests
The example/ app shows every screen inside the shell, plus a gallery of all widgets. Run it with cd example && flutter run -d chrome (or any device). Deep links like ?screen=invoices&theme=dark open a screen directly on web.
flutter test in the package root runs the widget tests, including a matrix that renders every screen at phone, tablet and desktop sizes in both themes and fails on any overflow. Keep it running as you customise and you’ll know when a change breaks a layout.
Something unclear? Get in touch.