octo_ui 1.0.0
octo_ui: ^1.0.0 copied to clipboard
Cross-platform Primer-inspired Flutter UI kit. Optimised for devtools, dashboards, and dense data-heavy interfaces.
octo_ui #
🎮 Live demo: https://mavoryl.github.io/flutter-octo-ui/ — the kitchen sink running in your browser. Updated on every push to main.
A cross-platform Flutter UI kit inspired by Primer. Optimised for devtools, dashboards, admin panels, and dense data-heavy interfaces — but every widget shares the same theme tokens, focus model, and accessibility baseline regardless of platform, so it runs cleanly on mobile too.
Why another UI kit #
Material is mobile-first and design-opinionated; Cupertino is iOS-locked; most boutique kits hard-code colours and skip keyboard navigation. octo_ui fills the gap in between:
- Tokens first, not components first — colours, spacing, radii, typography, shadows, breakpoints, and animation curves live in
OctoThemeData. Widgets read semantic tokens (theme.colors.fg.defaultColor,theme.colors.accent.emphasis) — never hardcoded values. - Every interactive state is mandatory — default, hover, focus, pressed, selected, disabled, loading. Web/desktop UIs without hover/focus look like stretched mobile apps;
octo_uiwidgets all wireFocusableActionDetector+WidgetStatesControllerso they cooperate with the rest of the Flutter ecosystem. - A11y is shipped, not added later —
Semanticsflags on every interactive surface,liveRegionon flash / toast, requiredsemanticLabelon icon-only buttons. WCAG-AA contrast is verified by automated tests across light / dark / high-contrast palettes. - Material adapter included —
octoTheme.toMaterialTheme()returns Material 3ThemeDataso dialogs, snackbars, popup menus, tooltips, and editing internals inherit Octo colours without you wiring them up.
Status #
Stable — 1.0.0. The theme API and every component API are frozen under
semantic versioning: additions land in minor releases, and nothing breaks
before 2.0.0.
What that covers: light and dark themes with high-contrast variants, an
accessibility baseline verified by tests, 37 components with golden coverage,
colour tokens generated from Primer Primitives, and per-component
documentation. What it does not: the colour-blind palette variants are enum
slots that still throw UnimplementedError, and lib/src/ remains private —
internal paths may move without a major bump.
Component catalogue #
37 components across 6 categories, each covered by unit and widget tests.
Form & input
OctoButton · OctoIconButton · OctoTextField · OctoSwitch · OctoCheckbox · OctoRadio · OctoSegmentedControl · OctoDropdown
Display & labels
OctoLabel · OctoCounterLabel · OctoStateLabel · OctoChip · OctoAvatar · OctoAvatarStack · OctoFlash · OctoSkeleton · OctoEmptyState
Navigation
OctoUnderlineNav · OctoSideNav · OctoTabs · OctoBreadcrumbs · OctoPagination
Overlays
OctoDialog · OctoTooltip · OctoMenu · OctoPopover · OctoToast · OctoCommandPalette (⌘K-style picker) · OctoActionList
Data & feedback
OctoDataTable<T> · OctoTimeline · OctoProgressBar · OctoSpinner
Layout primitives
OctoCard · OctoCollapsible · OctoDivider · OctoFilterBar
Documentation #
Every component carries a class-level doc comment with the same sections: a one-line summary, a usage sample, its variants, its sizes, the interactive states it tracks, and what it exposes to assistive technology. The API reference is the primary documentation — there is no separate site to drift out of sync with the code.
Both halves are enforced. The samples are type-checked against the real API by a test, so a renamed parameter breaks the build instead of shipping a snippet that doesn't compile, and CI fails on any unresolved doc reference.
Installation #
dependencies:
octo_ui: ^1.0.0
Or flutter pub add octo_ui.
Quick start #
import 'package:flutter/material.dart';
import 'package:octo_ui/octo_ui.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final octo = OctoThemeData.light();
return OctoTheme(
data: octo,
child: MaterialApp(
theme: octo.toMaterialTheme(),
home: const Scaffold(body: Center(child: _Example())),
),
);
}
}
class _Example extends StatelessWidget {
const _Example();
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const OctoStateLabel(
label: 'Open',
variant: OctoStateLabelVariant.open,
),
const SizedBox(height: 16),
OctoButton.label(
'Save changes',
onPressed: () {},
variant: OctoButtonVariant.primary,
),
],
);
}
}
A full kitchen sink — every component with controlled state, hover / focus / pressed showcases, dark-mode + high-contrast toggles, and a ⌘K command palette — lives in example/:
cd example
flutter run -d macos # or -d chrome, -d linux, -d windows, -d ios, -d android
Theming #
OctoTheme is both an InheritedTheme (propagates through Dialog / PopupMenu / Tooltip via InheritedTheme.captureAll) and a ThemeExtension<OctoThemeData> (so Theme.of(context).extension<OctoThemeData>() works in Material descendants).
final theme = OctoTheme.of(context);
final paragraphColor = theme.colors.fg.defaultColor;
final cardPadding = theme.spacing.gap.md;
final radius = BorderRadius.all(Radius.circular(theme.radii.medium));
Three built-in variants: OctoThemeData.light(), .dark(), and OctoColorSchemeVariant.highContrast for either brightness — WCAG-AA verified by tests.
Where the colours come from #
The four palettes and the breakpoint scale are generated from a pinned
@primer/primitives snapshot, not
typed by hand, so every value traces back to an upstream token and a tarball
digest. The snapshot is committed — builds never reach the network — and CI
fails if the generated file and the snapshot disagree.
OctoRadius, OctoTypography, OctoShadows and OctoAnimation stay
hand-written: Primer states those as CSS shorthand, or on a step scale that
doesn't line up with Flutter's — borderRadius-small is 3 px upstream against
4 px here, and there is no 10 px type step for labelSmall at all.
Architecture #
lib/
octo_ui.dart # public barrel — only export surface
src/
tokens/ # OctoColorScheme, OctoSpacing, OctoRadius,
# OctoTypography, OctoShadows, OctoBreakpoints,
# OctoAnimation
theme/ # OctoThemeData, OctoTheme, toMaterialTheme()
foundation/ # OctoBox, OctoText, OctoIcon,
# OctoFocusRing, OctoStateLayer
components/<name>/ # one folder per component, internals private
tokens/generated/ # emitted from the Primer snapshot — never edited
The public API is reachable only through package:octo_ui/octo_ui.dart. Internal paths under lib/src/ are private and may move without a breaking-change bump.
The package re-exports OctIcons from flutter_octicons so callers can paint Octicons without an extra dependency.
Responsive layout #
Breakpoints are theme tokens (xs 320 · sm 544 · md 768 · lg 1012 · xl 1280 · xxl 1400, matching Primer viewports), and two APIs read them:
// Window size class — for decisions that follow the viewport.
if (context.isAtLeast(OctoBreakpoint.lg)) showSidebar();
// Available-space size class — for content that sits beside a sidebar,
// inside a split view, or in any box narrower than the window.
OctoResponsiveBuilder(
builder: (context, breakpoint) => GridView.count(
crossAxisCount: breakpoint.isAtLeast(OctoBreakpoint.lg) ? 4 : 2,
children: tiles,
),
)
Thresholds come from OctoThemeData.breakpoints, so copyWith re-tunes every call site at once.
Accessibility #
- Every interactive widget exposes
Semantics(button|toggled|selected|enabled|expanded, label)matching its current state. - Keyboard activation (Enter / Space / NumpadEnter) is wired via
FocusableActionDetector; arrow-key navigation is supported onOctoActionList,OctoCommandPalette, andOctoMenu. - The focus ring (
OctoFocusRing) only paints in keyboard mode —FocusManager.highlightModewatches for real keyboard events. - Live regions on
OctoFlashandOctoToastannounce status changes to screen readers. - Motion is respected: components with infinite animations (
OctoSpinner,OctoSkeleton,OctoProgressBarindeterminate) honourMediaQuery.disableAnimationsOfand fall back to a static frame. - Colour-pair contrast (foreground on canvas, foreground-on-emphasis on every status colour) is enforced by automated WCAG-AA tests across
light,dark,light-hc, anddark-hcpalettes.
Testing #
flutter analyze
flutter test
CI runs dart format + flutter analyze + flutter test on every push.
License #
MIT. See NOTICE for third-party attributions — Octicons are © GitHub, Inc. (MIT) and ship through flutter_octicons (BSD-3-Clause).
