kumo_ui

A mobile-first Flutter implementation of Cloudflare's Kumo UI design system, built on package:flutter/widgets.dart alone.

pub package version platforms web flutter

kumo_ui reconstructs the Cloudflare dashboard's dark, high-density visual language — a near-black canvas, layered surfaces, hairline borders and a single orange brand signal — without ever importing material.dart or cupertino.dart. Every widget is composed from widgets.dart primitives and resolves its colors from KumoTheme.of(context).

Screenshots

Phone captures of the bundled example/ app in the dark scheme, top to bottom. Tap an image for the full-size version.

Header, segmented control, text inputs, toggles and accordion List group, expandable resource cards and the responsive split Breadcrumb, tabs and status badges
Checkbox, select, pagination and a toast banner Code block with copy action and the select bottom sheet Modal dialog with a nested code block

Platform support

Platform Status
Android Supported
iOS Supported
macOS Supported
Linux Supported
Windows Supported
Web Not supported — throws UnsupportedError

Platform & architectural philosophy

  • Zero Material, zero Cupertino. The library imports only package:flutter/widgets.dart, package:flutter/services.dart (clipboard services), package:flutter/foundation.dart (the kIsWeb platform guard) and package:phosphor_icons/phosphor_icons.dart. There are no MaterialApp, Theme, Card or InkWell dependencies to leak into your widget tree, so kumo_ui composes cleanly with any navigation or state solution you already use.
  • Single-import ergonomics. The core widgets.dart primitives are re-exported from kumo_ui.dart, so an app builds a whole screen from one import. Material and Cupertino are still never exposed.
  • Mobile-first, then adaptive. Layouts are designed at phone widths first: full-width tap targets with a 48px minimum height, vertical stacks instead of forced multi-column rows, and expandable cards for dense resource data. The same widgets then unfold into denser arrangements as the available width grows.
  • Adaptive to the container, not just the window. Adaptive widgets read their own LayoutBuilder constraints, so a sidebar, split pane or embedded view adapts on its own instead of assuming it owns the whole screen. The shared switchover point lives in kKumoBreakpoint (600 logical pixels).
  • An explicit web boundary. Cloudflare's canonical Kumo UI is the React/Tailwind implementation that ships with the dashboard. To avoid fragmenting that ecosystem with an unofficial browser re-implementation, kumo_ui refuses to run on Flutter Web: KumoTheme.of re-asserts kIsWeb and throws UnsupportedError. This package makes no claim to, and does not reproduce, Cloudflare's official web assets or trademarks.

Getting started

Add the package and its icon dependency to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  kumo_ui: ^1.7.0
  phosphor_icons: ^3.0.1

Then import it:

import 'package:kumo_ui/kumo_ui.dart';

That single import is enough to build a screen. kumo_ui re-exports the core Flutter primitives it is itself built from, grouped as:

  • Core framework — Widget, StatelessWidget, StatefulWidget, State, InheritedWidget, BuildContext, Key, ValueKey, GlobalKey.
  • Layout — Column, Row, Stack, Positioned, Expanded, Flexible, Spacer, Container, SizedBox, Padding, Align, Center, ConstrainedBox, BoxConstraints, Wrap, CrossAxisAlignment, MainAxisSize, Builder.
  • Geometry, paint and insets — EdgeInsets, EdgeInsetsGeometry, Alignment, BorderRadius, BoxDecoration, Border, BorderSide, BoxShape, Color, ColoredBox, Canvas, Paint, PaintingStyle, Path, Rect, Offset, Size, StrokeCap, StrokeJoin.
  • Text — Text, TextStyle, TextEditingController, TextOverflow.
  • Scrolling — ListView, SingleChildScrollView, CustomScrollView, SliverList, SliverGrid.
  • Interaction, focus and semantics — GestureDetector, MouseRegion, Focus, FocusNode, Semantics, SystemMouseCursors, VoidCallback, ValueChanged.
  • Animation — AnimatedContainer, AnimatedOpacity, AnimatedCrossFade, SizeTransition, AnimationController.
  • Navigation and scaffolding — runApp, WidgetsApp, Navigator, PageRouteBuilder, RouteSettings, WidgetBuilder, MediaQuery, LayoutBuilder, SafeArea, Brightness.
  • Router (Navigator 2.0) — RouteInformation, RouteInformationProvider, RouteInformationParser, RouterConfig, RouterDelegate, BackButtonDispatcher.

Two things worth knowing about those re-exports:

  • They come from package:flutter/widgets.dart only. Material and Cupertino symbols are never exposed. Because both paths resolve to the same Flutter declarations, importing kumo_ui next to material.dart cannot produce an ambiguous name.
  • It is a curated subset, not the whole of widgets.dart. If you reach for something outside it, add package:flutter/widgets.dart yourself — the compiler will point at the missing name immediately.

The paint primitives are in the list because the chart subsystem's public surface is built from them: a KumoChartLayer is a CustomPainter, and a KumoCanvas painter is handed a raw Canvas, so drawing your own chart needs Canvas, Paint, Path, Rect and Offset from the same single import.

The Phosphor glyph constants still come from phosphor_icons, since that is where the icon names are declared:

import 'package:phosphor_icons/phosphor_icons.dart';

kumo_ui targets Android, iOS, macOS, Linux and Windows. Do not add a web target; the package will throw at runtime if you do.

Color modes

Both schemes ship in the box — KumoColors.dark() and KumoColors.light(). KumoThemeMode picks between them, and KumoApp is the widget that applies the result:

KumoApp(mode: KumoThemeMode.system, home: const HomeScreen())

system is the default, so an app follows the platform setting and repaints when it changes. KumoTheme itself stays neutral and renders whatever KumoColors it is handed, which is what lets a single screen pin a scheme without touching the app.

// Resolve a scheme from a brightness...
final colors = KumoColors.of(MediaQuery.platformBrightnessOf(context));

// ...or name one directly.
const light = KumoColors.light();
const dark = KumoColors.dark();       // same tokens as `const KumoColors()`
Canvas Surface Recessed Border Text Brand
Dark #111111 #1D1D1D #262626 #333333 #EDEDED #F38020
Light #F7F7F7 #FFFFFF #EAEAEA #D4D4D8 #111111 #B03A0A

The light brand orange is a deeper step on purpose: #F38020 measures 2.6:1 on a white canvas, which is fine for a fill and not enough for text. Every text token in both schemes clears WCAG 2.1 AA (4.5:1) against every surface it can be painted on, and every non-text token clears 3:1. Both schemes are asserted by the test suite rather than checked by eye.

KumoColors.brightness reports which scheme a token set is, for the rare case where consumer code needs to branch on the mode.

Text follows the scheme

KumoTypography's statics are the scale with the dark tones applied, which keeps them safe to use outside a theme. A widget painting inside a themed surface should use the resolved set instead, so its text tracks the active scheme:

final styles = KumoTheme.textStylesOf(context);
Text('Zone settings', style: styles.h2);

KumoTheme.textStylesOf memoizes per KumoColors, and KumoTypography.resolve(colors) builds the same set when the tokens are already in hand.

Usage

1. Start with KumoApp

KumoApp is the MaterialApp equivalent: one widget that wires the color scheme, the navigator, the root text style and the platform brightness. Nothing else is needed to get a screen on device.

import 'package:kumo_ui/kumo_ui.dart';

void main() {
  runApp(KumoApp(home: const HomeScreen()));
}

It follows the platform setting by default. Pin a scheme, or replace either one, when the app should own that decision:

KumoApp(
  title: 'Kumo',
  mode: KumoThemeMode.dark,
  dark: KumoColors.dark(primary: brandOrange),
  home: const HomeScreen(),
)

KumoApp asserts the platform boundary on mount, so a Flutter Web build fails immediately instead of rendering an unofficial Kumo surface. Underneath it is a KumoTheme wrapping a WidgetsApp — reach for those two directly only when you need the theme placed below the navigator, which is something the packaged components already handle for themselves.

Routing with Navigator 2.0

KumoApp.router is the MaterialApp.router equivalent, so go_router and auto_route plug in the way they already do:

final router = GoRouter(
  routes: <RouteBase>[
    GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
  ],
);

KumoApp.router(routerConfig: router)

A raw RouterDelegate and RouteInformationParser work too, mirroring WidgetsApp.router. home, routes, initialRoute, onGenerateRoute and navigatorKey are deliberately absent from this constructor: a router owns the navigator, and WidgetsApp.router asserts they cannot be combined.

Two things worth knowing before you pick a router:

  • go_router stays Material-free here. It chooses its page type from the app it finds itself in — MaterialPage under a MaterialApp, NoTransitionPage under a plain WidgetsApp. Under KumoApp.router no Material widget enters your tree. The trade-off is that routes do not animate.
  • auto_route needs a nudge. RootStackRouter.config() is a RouterConfig, so it plugs in directly, but auto_route's default RouteType.material() renders a MaterialPage. Pick a non-Material route type if the zero-Material guarantee matters to you.

One router-specific gotcha worth stating plainly: a route builder runs once per location, not once per rebuild of your app's State. Anything a routed screen needs from the app — the way the example shares its theme mode — has to reach it through context, not a constructor argument.

2. Build a mobile list with KumoListGroup and KumoListItem

import 'package:kumo_ui/kumo_ui.dart';
import 'package:phosphor_icons/phosphor_icons.dart';

class SettingsList extends StatelessWidget {
  const SettingsList({super.key});

  @override
  Widget build(BuildContext context) {
    return KumoListGroup(
      title: 'Zone settings',
      children: [
        KumoListItem(
          title: 'DNS records',
          subtitle: '42 records',
          leading: const PhosphorIcon(PhosphorIconsRegular.globe, size: 18),
          onTap: () {},
        ),
        KumoListItem(
          title: 'Caching',
          subtitle: 'Standard',
          leading: const PhosphorIcon(PhosphorIconsRegular.lightning, size: 18),
          trailing: const KumoSwitch(value: true, onChanged: _noop),
        ),
      ],
    );
  }

  static void _noop(bool _) {}
}

Rows keep a 56px minimum height, share one rounded outline, and are separated by 1px dividers. A trailing caret appears automatically on any row that has an onTap.

3. Render expandable KumoDataCard resources in a KumoDataGrid

KumoDataGrid stacks its children on phones and lays them out as a bordered multi-column grid on wider containers, reusing the very same cards in both arrangements.

import 'package:kumo_ui/kumo_ui.dart';

class ZoneOverview extends StatelessWidget {
  const ZoneOverview({super.key});

  @override
  Widget build(BuildContext context) {
    return KumoDataGrid(
      maxColumns: 3,
      children: [
        const KumoDataCard(
          title: 'example.com',
          subtitle: 'Zone · Pro plan',
          statusLabel: 'Active',
          details: [
            KumoDataPair(label: 'Nameservers', value: 'ada.ns.cloudflare.com'),
            KumoDataPair(label: 'Records', value: '42'),
          ],
        ),
        KumoDataCard(
          title: 'Global DNS',
          subtitle: 'Zone · Free plan',
          statusLabel: 'Pending',
          // Pull a status token straight from the palette.
          statusColor: KumoTheme.of(context).warning,
          details: const [
            KumoDataPair(label: 'Nameservers', value: 'bob.ns.cloudflare.com'),
          ],
        ),
      ],
    );
  }
}

Each card reveals its key/value pairs when its header is tapped. On desktop viewports those pairs sit side by side; on phones each pair stacks, so long values never overflow.

4. Adapt between mobile and desktop with KumoResponsiveLayout

import 'package:kumo_ui/kumo_ui.dart';

class AdaptivePanel extends StatelessWidget {
  const AdaptivePanel({super.key});

  @override
  Widget build(BuildContext context) {
    return KumoResponsiveLayout(
      breakpoint: kKumoBreakpoint,
      mobile: const Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          KumoAccordion(
            title: 'Advanced settings',
            child: Text('Zone tag and routing rules.'),
          ),
          SizedBox(height: 12),
          KumoCodeBlock(code: 'npx wrangler deploy'),
        ],
      ),
      desktop: const Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: KumoAccordion(
              title: 'Advanced settings',
              child: Text('Zone tag and routing rules.'),
            ),
          ),
          SizedBox(width: 16),
          Expanded(child: KumoCodeBlock(code: 'npx wrangler deploy')),
        ],
      ),
    );
  }
}

KumoResponsiveLayout decides from its own constraints, so it also adapts correctly inside a sidebar or split pane. When a widget needs the same decision outside a builder, the KumoResponsiveLayout.isDesktop helper answers it from the window MediaQuery instead.

5. Chart a stream

Charts are a pure-Dart subsystem under lib/src/charts/. A data tick repaints only the dynamic layer: the grid rasterises once per layout, and the widget tree is not rebuilt for a tick at all.

final KumoSeriesBuffer series = KumoSeriesBuffer(capacity: 4096);
final KumoTimeWindow window = KumoTimeWindow.last(
  const Duration(minutes: 5),
  endTimestamp: now,
);
final KumoChartController<double> controller = KumoChartController<double>(
  capacity: 4096,
);

series.add(now, 42);
controller.append(now);

KumoTimeseriesChart(series: series, window: window, repaint: controller);

Three families ship — KumoTimeseriesChart, KumoSankeyChart and KumoGeoMapChart — plus KumoChartContainer and KumoCanvas for anything else. KumoCanvas is the drawing escape hatch: it manages the surface, the gridlines, the tokens and the repaint split, and hands you a raw Canvas.

KumoCanvas(
  repaint: controller,
  painter: (Canvas canvas, Size size, KumoChartContext context) {
    canvas.drawPath(myPath, myPaint);   // context.geometry.plot is the data area
  },
)

See the chart reference.

Component catalog

App scaffolding

  • KumoApp — the whole app in one widget: resolves the scheme from KumoThemeMode, then wraps KumoTheme around a WidgetsApp with a derived root text style and task-switcher color. The MaterialApp equivalent, without Material.
  • KumoApp.router — the same, but the navigator comes from a RouterConfig (go_router, auto_route) or a raw RouterDelegate plus RouteInformationParser. The MaterialApp.router equivalent.

Form and input

  • KumoInput — text field with label, placeholder, prefix/suffix icons, obscured entry, inline error state and multi-line mode via maxLines.
  • KumoSwitch — compact on/off toggle with a 150ms eased thumb slide.
  • KumoCheckbox — labelled checkbox that flips to the brand fill when checked, with the whole row as one 48px tap target. The box is KumoCheckbox.boxSize (18px) square.
  • KumoSelect<T> — single-choice field that opens a popover on desktop and a KumoBottomSheet action sheet on phones.
  • KumoSegmentedControl<T> — fully controlled horizontal segmented selector for switching between sibling views.
  • KumoButton — action with a 48px touch target and optional Phosphor icon, in a KumoButtonVariant: primary (brand fill) or secondary (outlined).
  • KumoRadio<T> — mutually exclusive choice that deliberately mirrors KumoCheckbox: an 18px control, the whole row as one 48px tap target, the brand signal when chosen. Compare value with groupValue yourself.
  • KumoSensitiveInput — a secret the user has to paste and verify, so it adds the one thing KumoInput alone cannot give it: a reveal toggle.

Navigation

  • KumoBreadcrumb — scrollable parent-route trail with caret separators and a non-interactive current page. Each step is a KumoBreadcrumbItem; the one without an onTap is the current page.
  • KumoTabs — tab bar with a 2px brand indicator under the active tab.
  • KumoPagination — Prev/Next switcher with a monospace Page X of Y indicator.
  • KumoDrawerScaffold — the navigation shell: a KumoDrawer docked beside the page at or above kKumoBreakpoint, or a slide-over sheet with a scrim below it, closing on Escape with focus moved inside. open, close, isDocked and hasScaffold are statics, and the first three need a context below the scaffold — build the drawer through a Builder when its rows call close.
  • KumoDrawer — the panel itself: an optional pinned header and footer around a scrollable nav area, and an icon-only collapsed rail.
  • KumoDrawerItem — one 48px nav row carrying Semantics(selected:), so the current destination is announced rather than only painted. KumoDrawerGroup labels a run of them.

Structure and layout

  • KumoScaffold — the screen shell: canvas, system insets, keyboard avoidance, a pinned header, a scrolling body and an optional bottomBar. Replaces the ColoredBox + SafeArea + SingleChildScrollView boilerplate.
  • KumoHeader — section header that stacks its action under the title on phones and aligns it across the row on desktop.
  • KumoListGroup — bordered group that separates KumoListItem rows with 1px dividers.
  • KumoDataGrid — adaptive card container: a vertical stack on phones, a bordered multi-column grid on desktop.
  • KumoDataCard — expandable resource card with a status chip and adaptive key/value details, each detail a KumoDataPair.
  • KumoResponsiveLayout — swaps any two arrangements at a configurable breakpoint based on the local constraints.
  • KumoAccordion — collapsible section using a SizeTransition reveal.
  • KumoBottomSheet — bottom-anchored surface pushed with KumoBottomSheet.show<T>(…), for touch-first choices. Its rows are KumoBottomSheetItems at a full 48px tap height.
  • KumoDataGridScope — internal plumbing shared by KumoDataGrid and KumoDataCard: it tells a card it is a grid cell so it drops its own outline rather than doubling the divider. Exported because Dart has no package-private, not because you should reach for it.

Content and overlay

  • KumoCodeBlock — monospace code surface with an optional language label and a clipboard copy action.
  • KumoModal — modal dialog pushed with KumoModal.show<T>(…) on a custom RawDialogRoute, with a dimmed barrier and a centered card. Both show methods resolve to a Future<T?> when the route pops.
  • KumoToast and KumoToastManager — stacked, auto-dismissing status banners overlaid at the bottom centre. KumoToastManager.show(context, …) pushes one, and dismiss(id) / clear() / activeCount manage the stack. Auto-dismiss waits KumoToastManager.defaultDuration (4s) by default.
  • KumoBadge — 11px monospace pill tag for metadata, plan types and status flags, in a KumoBadgeVariant: info, success, warning, error or neutral.

Feedback and primitives

  • KumoLoader — indeterminate progress arc, drawn with a CustomPaint painter instead of Material's indicator.
  • KumoMeter — determinate meter with a tone (primary, success, warning, danger) and an optional label and percentage.
  • KumoEmpty — empty state carrying a reason and, optionally, the action that fills the screen.
  • KumoSkeleton — static placeholder bar, or a stack of them for a text block.
  • KumoLink — brand-coloured text link that keeps a 48px target and activates on Enter or Space.
  • KumoLabel — the uppercase micro label the field components paint.
  • KumoTooltip — message on hover or long press, placed top (the default) or bottom of its child, leaving the child's own tap behaviour alone.
  • KumoFocusable — the focus-ring, Enter/Space activation and hit-cursor wrapper every interactive component here is built from. Reach for it to give a custom control the same contract.

Charts

  • KumoChartContainer — the chart surface. It takes a static background layer and a repaint-driven foreground layer and splits them, so a data tick repaints the dynamic layer only and the grid rasterises once per layout. KumoChartLayer is the base class: prepare() runs once per layout and owns every Paint and Path, so paint() allocates nothing. KumoChartGeometry and KumoChartContext carry the plot rect, the resolved tokens and the repaint listenable into a layer.
  • KumoCanvas — the escape hatch for anything the families do not cover: a KumoCanvasPainter callback taking a raw Canvas, the surface Size and a pre-computed KumoChartContext, with the surface, plot rect, gridlines, axis ticks and repaint split managed around it. KumoCanvasGridLayer is the grid it draws behind you.
  • KumoTimeseriesChart — streaming line and area over a sliding window. KumoSeriesBuffer is fixed-capacity point storage that never allocates on append; KumoTimeWindow is the pure-Dart viewport, with Matrix4 transforms and caller-buffer projection; KumoLttb downsamples to the plot's pixel width while keeping the peaks.
  • KumoSankeyChart — flow between stages, laid out by KumoSankeySolver from a KumoSankeyGraph of KumoSankeyNodes and KumoSankeyLinks. The solver is usable on its own — it writes into caller-owned KumoSankeyNodeLayout and KumoSankeyLinkLayout structs — and it terminates on cycles rather than hanging.
  • KumoGeoMapChart — choropleth from GeoJSON, together with KumoGeoJsonParser (a decoded Map into a KumoGeoMapData of KumoGeoFeatures), KumoGeoProjection / KumoGeoProjectionKind (mercator or equirectangular), KumoGeoBounds and KumoGeoProjector.
  • KumoChartColors — the chart palette: semantic status tones, a five-slot categorical cycle and a sequential ramp. Kept outside KumoColors because it is scheme-independent, so a series reads the same in light and dark.
  • KumoChartController, KumoDataBuffer, KumoRingBuffer and KumoStreamingChartSource — the streaming path. Fixed-capacity rings plus one coalesced flush per frame mean a fast socket cannot become a fast rebuild. Hand a controller to a chart's repaint and a tick repaints the layer instead of the widget.
  • The layer classes are public, so a chart can be composed and not only configured: KumoTimeseriesBackgroundLayer / KumoTimeseriesForegroundLayer, KumoSankeyBackgroundLayer / KumoSankeyForegroundLayer and KumoGeoMapLayer. KumoChartLayerBuilder, KumoCanvasPainter, KumoAxisLabelBuilder and KumoFlushScheduler are the callback typedefs those pieces take.

Enums

  • KumoButtonVariant — primary, secondary.
  • KumoBadgeVariant — info, success, warning, error, neutral.
  • KumoBannerKind — info, success, warning, error.
  • KumoToastKind — info, success, warning, error.
  • KumoMeterTone — primary, success, warning, danger.
  • KumoTooltipPlacement — top, bottom.
  • KumoThemeMode — system, light, dark.
  • KumoGeoProjectionKind — mercator, equirectangular.

Theming

  • KumoTheme — InheritedWidget that supplies the palette. KumoTheme.of returns the nearest ancestor's colors, falling back to const KumoColors() when there is none, and asserts the platform guardrail on every read. KumoTheme.ensureSupportedPlatform() performs that check once at startup.

  • KumoPalette — the raw dark scale: a ten-step gray0–gray9 ramp, darkest to lightest, plus orange5, blue5, red5, green5 and amber5 accents.

  • KumoLightPalette — the raw light scale, mirroring it step for step, so a token reads the same index in either scheme. Its accents are deeper, because the dark ones do not survive a light surface.

  • KumoColors — the semantic layer. Every token resolves to a step of the active scheme's palette:

    Token Dark Light Role
    canvas #111111 #F7F7F7 Page background
    surface #1D1D1D #FFFFFF Default raised surface
    subtleSurface #262626 #EAEAEA Recessed fill (inputs, toggles, badges)
    border #333333 #D4D4D8 Hairline dividers and outlines
    primary #F38020 #B03A0A Brand signal and active fill
    focus #E9E9E9 #111111 Focus ring
    textPrimary #EDEDED #111111 High-emphasis text
    textSecondary #A1A1AA #52525B Labels, hints, placeholders
    textMuted #8D8D99 #5A5F6B Lowest-emphasis AA text
    info #0EA5E9 #0369A1 Info status
    success #34D399 #036B4E Success status
    warning #F59E0B #9A4D08 Warning status
    danger #EF4444 #DC2626 Destructive indicator only
    dangerText #F87171 #B91C1C Destructive text (contrast-safe)
    scrim 60% black 20% black Modal and bottom-sheet barrier wash

    Keep danger for fills, icons and borders; use dangerText whenever the red is the text itself, because danger sits below 4.5:1 on the recessed surfaces in both schemes.

  • KumoColors.dark() / KumoColors.light() / KumoColors.of(brightness) — the two schemes and the lookup between them. The unnamed const KumoColors() is the dark scheme, so it stays a valid default.

  • KumoTextStyles and KumoTypography.resolve(colors) — the type scale tinted for one scheme. KumoTheme.textStylesOf(context) resolves it for whichever scheme is in scope, and is what the components paint with.

  • KumoTypography — h1, h2, body, bodyMuted, caption and code text styles, carrying the dark tones.

  • kKumoBreakpoint — the shared 600px mobile/desktop switchover point.

Component checklist

Coverage against Cloudflare's upstream Kumo component set. [x] means this package ships a Flutter equivalent; [ ] means it does not yet. The same list, with a drift check, lives in skills/kumo-registry/.

Implemented — 26 of 45

  • x Badge — KumoBadge
  • x Banner — KumoBanner
  • x Breadcrumbs — KumoBreadcrumb
  • x Button — KumoButton
  • x Checkbox — KumoCheckbox
  • x Collapsible — KumoAccordion
  • x Dialog — KumoModal
  • x Dropdown — KumoSelect
  • x Empty — KumoEmpty
  • x Input — KumoInput
  • x InputArea — KumoInput(maxLines: n)
  • x Label — KumoLabel
  • x Link — KumoLink
  • x Loader — KumoLoader
  • x Meter — KumoMeter
  • x Pagination — KumoPagination
  • x Radio — KumoRadio
  • x Select — KumoSelect
  • x Sensitive Input — KumoSensitiveInput
  • x Sidebar — KumoDrawerScaffold
  • x Skeleton Line — KumoSkeleton
  • x Switch — KumoSwitch
  • x Tabs — KumoTabs
  • x Text — KumoTypography
  • x Toast — KumoToast
  • x Tooltip — KumoTooltip

Partial — 6 — the capability exists inside another widget rather than as one of its own:

  • x Clipboard Text — the KumoCodeBlock copy action
  • x CodeHighlighted — KumoCodeBlock renders monospace, with no highlighting
  • x Grid — KumoDataGrid is a card grid, not a general layout grid
  • x Inline Copy Text — the KumoCodeBlock copy action
  • x Layer Card — KumoDataCard is a resource card, not a general layer
  • x Table — KumoDataGrid lays out cards, not a true data table

Not implemented — 12

  • Autocomplete
  • Button Group
  • Combobox
  • Command Palette
  • Date Picker
  • Flow
  • InputGroup
  • Layer Dialog
  • Popover
  • Table of Contents
  • Tag Input
  • Toolbar

Charts — a pure-Dart subsystem in lib/src/charts/, with no chart package underneath, documented in skills/flutter-kumo-ui/references/charts.md.

  • x Timeseries — KumoTimeseriesChart, with KumoTimeWindow, KumoSeriesBuffer and LTTB downsampling
  • x Sankey — KumoSankeyChart, on KumoSankeySolver
  • x Maps — KumoGeoMapChart, with KumoGeoJsonParser and KumoGeoProjection
  • x Custom Chart — KumoCanvas is the composable escape hatch: a painter callback handed a raw Canvas, with the gridlines, axis ticks, tokens and repaint split managed around it. KumoChartContainer and KumoChartLayer remain the layer-level route for a chart assembled from layers.

Deliberately excluded

  • Cloudflare Logo — this package is unaffiliated and ships no Cloudflare marks.

Beyond upstream

  • x KumoScaffold — the screen shell. Kumo has no scaffold equivalent, so it is not one of the 45 above.

Example

A runnable showcase lives in example/. It exercises the component set at a phone and a desktop viewport, and it demonstrates the chart subsystem and the drawer shell.

The root is a single KumoApp.router driving a go_router RouterConfig. A ShellRoute wraps every page in a KumoDrawerScaffold — a docked rail on a wide window, a sheet over the page on a narrow one — and the drawer's highlight is derived from the active route path, so navigation and the drawer cannot disagree:

  • /overview — the component gallery
  • /charts/timeseries — a live series fed by a simulated socket, streaming through KumoChartController into KumoTimeseriesChart
  • /charts/sankey — KumoSankeyChart next to the KumoSankeySolver readout
  • /charts/geomap — KumoGeoMapChart over a synthetic choropleth
  • /charts/custom — a bespoke visualiser drawn with KumoCanvas
  • /settings — a second-level route, pushed onto the same router

It imports kumo_ui, phosphor_icons and go_router, and nothing else.

The System / Light / Dark selector switches the scheme live by changing KumoApp.mode. Because a route builder does not re-run when the app rebuilds, the mode reaches the pages through a small InheritedWidget rather than a constructor — the pattern any routed app needs. The KumoSelect example is what surfaces KumoBottomSheet on phones, and a direct KumoBottomSheet.show section sits beside it.

cd example
flutter run

Build with a coding agent

The repository ships an agent skill at skills/flutter-kumo-ui/ that teaches a coding agent this package's API, conventions and pitfalls, so it writes kumo_ui code instead of guessing and reaching for Material. It follows the Agent Skills standard, so it is agent-agnostic: Claude Code, Codex, Cursor, Gemini CLI, Command Code and anything else implementing the standard can read it.

npx skills add hafizhfadh/flutter_kumo_ui

The skill covers app bootstrap (KumoApp / KumoApp.router), the light and dark schemes, the resolved text styles, every component constructor with its exact parameters and defaults, and the checklist a change has to satisfy before it is done.

Additional information

  • Contributions, bug reports and feature requests are welcome via the issue tracker.
  • The package page, changelog and API docs live on pub.dev.
  • kumo_ui follows semantic versioning from 1.0.0: additive API ships in minor releases, and fixes or internal changes ship in patches. See CHANGELOG.md for the detail of each release.

Disclaimer & Trademark Notice

kumo_ui is an independent, community-driven Flutter package inspired by Cloudflare's Kumo UI design system.

  • Independent Project: This package is NOT affiliated, endorsed, sponsored, or maintained by Cloudflare, Inc.
  • Trademarks: "Cloudflare", "Kumo", and "Kumo UI" are registered trademarks of Cloudflare, Inc.
  • Platform Scope: This package is explicitly designed for native mobile and desktop Flutter applications. Web targets are intentionally disabled to respect Cloudflare's canonical React web component ecosystem (@cloudflare/kumo).

Libraries

kumo_ui
A mobile-first Flutter implementation of Cloudflare's Kumo UI design system, built on package:flutter/widgets.dart alone.