willink_theme 1.5.0
willink_theme: ^1.5.0 copied to clipboard
i-Willink Design System theme for Flutter — Material 3 ThemeData factories on a single-brand baseline, consumer-overridable via ColorScheme copyWith.
Changelog #
All notable changes to willink_theme will be documented here.
Format: Keep a Changelog.
This project follows the 0.x semver convention (minor bumps may include
breaking changes; pin with ~0.1.0 for exact-minor stability).
1.5.0 — 2026-06-11 #
Added — WillinkTheme.willinkDark() (dark mode, ADR-0013) #
The Flutter side of the v1.2 dark-mode cycle
(docs/roadmap/v1.2.md): a Brightness.dark
ThemeData factory mirroring the willink.dark semantic flips that
@willink-labs/tokens semantic.json ships for the web. Same
semantic-flip / primitive-invariant contract — brand identity does not
flip; only surface roles do:
| ColorScheme slot | light | dark | semantic role |
|---|---|---|---|
surface |
#ffffff |
neutral-950 |
bg |
onSurface |
neutral-900 |
neutral-50 |
fg |
onSurfaceVariant |
(derived) | neutral-400 |
muted |
surfaceContainerLow / Container |
(derived) | neutral-900 |
surface-subtle |
surfaceContainerHigh |
(derived) | neutral-800 |
surface-muted |
surfaceContainerHighest |
neutral-100 |
neutral-700 |
track (WillinkProgressIndicator) |
outline / outlineVariant |
neutral-200 |
neutral-800 |
border |
primary / onPrimary |
brand-600 / white |
unchanged | brand / brand-fg (mode-invariant) |
primaryContainer (+ secondary) |
brand-100 |
brand-950 |
brand-soft |
onPrimaryContainer (+ secondary) |
brand-900 |
brand-300 |
brand-soft-fg |
tertiary |
cyan-500 |
unchanged | accent-cyan (mode-invariant) |
error |
red-600 |
red-500 |
danger |
WillinkBrandTokens.willinkDarkrides the darkThemeData: the white-anchoredsubtleGradientflips to the dark surface ladder (neutral-950 → brand-950 → neutral-900, the preset's darkbg-gradient-subtlederivation) andshadowSoftraises its black alpha (0.05 → 0.4, the dark--shadow-soft);brandGradient/aiGradient/brandGlow/shadowGloware identical to light.- Every component theme slot the light factory sets is mirrored — both factories now assemble through one shared builder, so light/dark cannot structurally drift.
- All 9 components follow automatically (they read
Theme.of(context).colorScheme); no component ships dark-specific styles, per the ADR-0013 "semantic flip only" rule.
Wire it up the standard Material way:
MaterialApp(
theme: WillinkTheme.willink(),
darkTheme: WillinkTheme.willinkDark(),
themeMode: ThemeMode.system,
)
Flutter-only minor per ADR-0011 — ships in the same cycle as the npm group's dark release but versions independently.
Migration from 1.4.0 #
No breaking changes. Additive release — WillinkTheme.willinkDark() and
WillinkBrandTokens.willinkDark are opt-in; WillinkTheme.willink() output
is unchanged.
Verification #
flutter analyze: 0 issuesflutter test: 66 tests pass (existing 58 + new 8 dark tests: 4 factory + WillinkButton ×2 / WillinkSnackBar / WillinkTabBar underwillinkDark())dart pub publish --dry-run: clean
1.4.0 — 2026-06-11 #
Added — WillinkProgressIndicator component (issue #13, PR 4/4) #
Material 3 brand-aware linear progress indicator, parity with the React DS
Progress (Radix Progress wrapper). Wraps LinearProgressIndicator with
theme-aware styling:
- determinate (
value: 0.0–1.0) and indeterminate (value: null) variants — the same nullable-value contract as both Material and the ReactProgress. Note the scale difference: React/Radix uses0–100, Flutter keeps the native0.0–1.0(documented in the dartdoc). - fill =
colorScheme.primary, track =colorScheme.surfaceContainerHighest(brand-neutral — the Material 3 equivalent of the React track'sbg-neutral-200), so the bar follows consumercopyWith(colorScheme: ...)overrides automatically. - 8dp tall by default (React DS
h-2; Material's native 4dp is available viaminHeight: 4) and fully rounded (rounded-full), overridable viaminHeight/borderRadius. semanticsLabelpassthrough (the React example'saria-label).
Positioning vs. WillinkLoadingState: LoadingState = full-area loading state
(centered spinner + caption replacing content); ProgressIndicator = inline
linear progress inside otherwise-rendered UI (uploads / multi-step flows).
This is a Flutter-only minor bump per
ADR-0011 — the npm
packages are unchanged. Completes the Phase-7 component expansion
(#13),
4th of 4 components (WillinkTabBar / WillinkBottomSheet /
WillinkSnackBar ship as 1.1.0–1.3.0).
Migration from 1.x #
No breaking changes. Additive release — WillinkProgressIndicator is opt-in.
Verification #
flutter analyze: 0 issuesflutter test: 37 tests pass (existing 30 + new 7 progress-indicator tests)dart pub publish --dry-run: clean
1.3.0 — 2026-06-11 #
Added — WillinkSnackBar component (#13, v1.1 parity PR 3/4) #
Material 3 brand-aware snack bar helper — parity with the React DS Toast
(brand-styled sonner wrapper). Thin wrapper over
ScaffoldMessenger.showSnackBar:
WillinkSnackBar.show(context, message: ...)— returns theScaffoldFeatureControllerfromshowSnackBar- variants:
info(default) /success/errorviaWillinkSnackBarVariant - neutral surface styling mirroring the React toast:
colorScheme.surfacebackground,outlineborder, 12px radius (radiusLg), floating behavior - semantics carried by the leading icon accent —
colorScheme.primary(info) /WillinkSemantics.success(success) /colorScheme.error(error) — so the snack bar follows any brand the consumer configures viacopyWith(colorScheme: ...) - optional
descriptionline (muted,onSurfaceVariant) - optional
actionLabel+onAction→SnackBarActionincolorScheme.primary - reuses Material 3 SnackBar timing and queueing (default 4s, overridable
duration); no custom queue logic
Flutter-only minor per ADR-0011 — the npm packages are unchanged and stay at their own versions.
Migration from 1.2.x #
No breaking changes. Additive release — WillinkSnackBar is opt-in.
Verification #
- flutter analyze: 0 issues
- flutter test: all pass (7 new snack bar tests)
- dart pub publish --dry-run: clean
1.2.0 — 2026-06-11 #
Added — WillinkBottomSheet component (1 component) #
Material 3 brand-aware modal bottom sheet mirroring the React DS Sheet
(<SheetContent side="bottom"> — mobile action sheet), as a thin wrapper
over Material 3 showModalBottomSheet:
WillinkBottomSheet.show<T>(context, builder: ...)static helper returningFuture<T?>(resolves with theNavigator.popvalue, ornullon barrier tap / drag-down dismiss)isScrollControlled/isDismissible/enableDrag/useSafeAreapass-through (same contract and defaults asshowModalBottomSheet)showDragHandleflag (defaulttrue) — renders a 32×4 rounded drag handle tinted withcolorScheme.outlineVariant(manual, sinceshowModalBottomSheetexposes no per-call handle color)WillinkBottomSheetwidget — minimal title + child content scaffold (title 18px / w600, mirrors ReactSheetTitle)- barrier is black at 50% alpha, matching the React Sheet overlay (
bg-black/50) - top corners use
WillinkPrimitives.radiusXl(16px — the DS radius feel, instead of the Material 3 default 28px)
Colors derive from Theme.of(context).colorScheme (sheet surface =
surface, drag handle = outlineVariant, title = onSurface) so the
sheet follows any brand the consumer configures via
copyWith(colorScheme: ...) automatically.
Second of the four v1.1 Flutter parity components carried over from v1.0 Phase 9.4 (#13); ships as an independent minor per ADR-0011.
Migration from 1.1.0 #
No breaking changes. Additive release — WillinkBottomSheet は opt-in。既存
components の API は完全互換。
Verification #
- flutter analyze: 0 issues
- flutter test: all pass (new 7 bottom sheet tests included; 37 on this branch — existing 30 + 7)
- dart pub publish --dry-run: clean
1.1.0 — 2026-06-11 #
Added — WillinkTabBar component (1 component) #
Material 3 brand-aware tab bar mirroring the React DS Tabs compound
(<Tabs><TabsList><TabsTrigger/>...), as a thin wrapper over Material 3
TabBar:
tabspass-through (List<Widget>, typicallyTabinstances)controller/ ancestorDefaultTabControllerselection state (same contract asTabBar)onTapcallback with the tapped indexisScrollableflag- implements
PreferredSizeWidgetso it slots intoAppBar.bottom
Colors derive from Theme.of(context).colorScheme (indicator + selected
label = primary, unselected label = onSurfaceVariant, divider =
outlineVariant) so the tab bar follows any brand the consumer configures
via copyWith(colorScheme: ...) automatically.
First of the four v1.1 Flutter parity components carried over from v1.0 Phase 9.4 (#13); ships as an independent minor per ADR-0011.
Migration from 1.0.0 #
No breaking changes. Additive release — WillinkTabBar は opt-in。既存 5
components の API は完全互換。
Verification #
- flutter analyze: 0 issues
- flutter test: 37 tests pass (existing 30 + new 7 tab bar tests)
- dart pub publish --dry-run: clean
1.0.0 — 2026-05-17 #
API freeze (coincidence cut with the npm group) #
First stable release. The WillinkTheme factory, WillinkSpacing scale, the 5 component widgets (WillinkButton, WillinkEmptyState, WillinkErrorState, WillinkLoadingState, WillinkSectionCard), and the tokens_sync_test.dart token-drift guard are now part of the SemVer-2.0 contract.
No content change vs. flutter-v0.5.0. The bump is a one-time storytelling coincidence with the npm group's 1.0.0 cut — from flutter-v1.0.1 onward willink_theme floats per ADR-0011. Future Flutter minors and patches track Flutter-side cadence; they do not have to wait for an npm release, and vice versa.
Out of v1.0 scope (deferred to v1.1+) #
- Phase 7 component expansion (
WillinkTabBar,WillinkBottomSheet,WillinkSnackBar,WillinkProgressIndicator) — deferred so the first-stable cut is not blocked. Each is planned as its own minor. - Dark mode variant — light-only ships first to keep the API surface fixed.
0.5.0 — 2026-05-16 #
Breaking — brand axis machinery removed #
WillinkBrand enum and per-brand factories (WillinkTheme.clublink() / .fitai() / WillinkTheme.fromBrand()) have been removed. WillinkTheme.willink() is now the single factory.
Removed:
lib/src/brand_axis.dart(entire file —WillinkBrandenum +toColorScheme())WillinkTheme.fromBrand(WillinkBrand)factoryWillinkTheme.clublink()factoryWillinkTheme.fitai()factoryWillinkBrandTokens.clublinkstatic getterWillinkBrandTokens.fitaistatic getterWillinkPrimitives.fitaiPrimary/fitaiSecondary/fitaiTertiaryconstantstest/brand_axis_test.dart(entire file)export 'src/brand_axis.dart'from package entrypoint
Kept:
WillinkTheme.willink()— the single Material 3 ThemeData factoryWillinkBrandTokens.willink— non-Material brand tokens (glow / gradient / shadow)- All
WillinkPrimitives.*color and motion constants (neutral / brand / blue / green / cyan / pink / sky / red / amber) - All component widgets (
WillinkEmptyState/WillinkErrorState/WillinkLoadingState/WillinkSectionCard/WillinkButton)
Migration #
- final theme = WillinkTheme.clublink();
+ final theme = WillinkTheme.willink().copyWith(
+ colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2563EB)),
+ );
- final theme = WillinkTheme.fitai();
+ final theme = WillinkTheme.willink().copyWith(
+ colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF3B82F6)),
+ );
For consumers that previously used WillinkBrandTokens.clublink / .fitai:
// 0.4.x:
final tokens = WillinkBrandTokens.clublink;
// 0.5.0+: construct your own custom WillinkBrandTokens:
final tokens = WillinkBrandTokens(
brandGlow: const Color(0xFF3B82F6),
brandGradient: const LinearGradient(
begin: Alignment.topLeft, end: Alignment.bottomRight,
colors: [Color(0xFF2563EB), Color(0xFF10B981)],
),
subtleGradient: WillinkBrandTokens.willink.subtleGradient,
aiGradient: WillinkBrandTokens.willink.aiGradient,
shadowSoft: WillinkBrandTokens.willink.shadowSoft,
shadowGlow: const [
BoxShadow(color: Color(0x4C2563EB), offset: Offset(0, 0), blurRadius: 20, spreadRadius: -5),
],
);
Why #
Matches the React side of the DS (which dropped the [data-brand="..."] mechanism in @willink-labs/tailwind-preset@0.8.0). ClubLink / fit-ai are independent products and now configure their own ColorScheme rather than relying on a per-product factory in DS.
Verification #
flutter analyze: 0 issuesflutter test: all pass (brand_axis_test.dart removed; theme_data_test.dart updated to single-brand assertions)dart pub publish --dry-run: clean
0.4.0 — 2026-05-14 #
Added — WillinkButton component (1 component) #
Material 3 brand-aware button with variant / size / icon API:
- variants:
filled(default) /outline/ghost - sizes:
small/medium/large - leadingIcon / trailingIcon support (8px gap)
- fullWidth flag
- automatic disabled state (when
onPressed == null→ opacity 0.5 + no ripple) - filled variant applies a brand-tinted glow shadow (
colorScheme.primaryat 30% alpha) - ghost variant uses
primaryContaineras hover/pressed overlay
Colors derive from Theme.of(context).colorScheme so the button follows the
active brand axis (willink / clublink / fitai) automatically.
採用想定先: clubhouse / fit-ai mobile (両者とも willink_theme 0.3.0 main 反映済)。
Migration from 0.3.x #
No breaking changes. Additive release — WillinkButton は opt-in。既存 4
components の API は完全互換。
Verification #
- flutter analyze: 0 issues
- flutter test: 39 tests pass (existing 33 + new 7 button tests)
- dart pub publish --dry-run: clean
0.3.0 — 2026-05-11 #
Added — DS Phase 6 Flutter components (4 件) #
最初の component layer。Material 3 theme-aware で全 brand axis (willink /
clublink / fitai) に追従。fit-ai mobile + clubhouse の重複実装
(EmptyState / ErrorState / LoadingState / SectionCard) を
willink_theme に一本化し、二重メンテを解消。
WillinkEmptyState— icon + title + description + optional CTA。 空データ scene 用。色はcolorScheme.onSurfaceVariant/outlineで 自動 brand 追従。WillinkErrorState— エラー表示 + copy-to-clipboard + retry button。AsyncValue.whenの error branch 用。icon はcolorScheme.error。WillinkLoadingState— 3 variants:- default (40px): full-screen loading
.compact()(24px): inline within sections.inline()(16px): bare spinner for buttons / list rows / dense layouts
WillinkSectionCard— title + trailing + child の section surface。 React DS のCardcompound に相当。Material 3 elevation tint。
Tests #
- 19 widget tests (
test/components_test.dart) — total 33 pass. flutter analyze: No issues found.
Consumer adoption plan #
- fit-ai mobile: dashboard (PR #1906 で部分採用) → 段階拡大
- clubhouse: Phase 0 全画面 (single-PR migration 推奨)
Migration from 0.2.x #
No breaking changes. Existing token / theme API 完全互換。
New widgets are opt-in (WillinkEmptyState 等を採用したい画面で import)。
0.2.1 — 2026-05-09 #
0.2.0 — 2026-05-09 #
Driven by feedback from the first round of consumer integration (Phase 5.3
fit-ai · Phase 5.4 clubhouse). Both apps had local AppSpacing scales and
multiple gradient presets that the 0.1.0 surface did not yet cover.
Added #
WillinkSpacing— Material 3 4-multiple spacing scale (xs=4 / sm=8 / md=16 / lg=24 / xl=32 / xxl=48). Importable asWillinkSpacing.mdetc., suitable forEdgeInsets.all(...).WillinkBrandTokens.subtleGradient— white → brand-50 → sky-50 diagonal, mirrors the React preset'sbg-gradient-subtle. For hero / large-surface backgrounds that shouldn't overpower the foreground.WillinkBrandTokens.aiGradient— cyan → brand-500 → pink, mirrorsbg-gradient-ai. For "AI"-flavored UI moments.
Changed #
WillinkBrandTokensconstructor now requiressubtleGradient+aiGradient. This is a breaking change for direct callers of the constructor. The three named presets (willink/clublink/fitai) absorbed the change internally, so consumer apps usingWillinkTheme.fromBrand(...)are unaffected. Per the 0.x convention, breaking changes ship in minor bumps.
Why not TextTheme override / dark factory yet #
fit-ai's AppTextStyles (7 sizes, including a non-Material 18px title) does not map cleanly onto Material 3's TextTheme. We're holding off on adding a DS-side TextTheme override until we can make the choice well — likely 0.3.0 together with a dark factory, after one more sweep round on fit-ai informs the canonical scale.
0.1.0 — 2026-05-08 #
Initial scaffold of the Flutter side of the i-Willink Design System.
License — MIT (differs from npm packages) #
This package ships under MIT, distinct from the existing
@willink-labs/{tokens,tailwind-preset,react} npm packages which remain
UNLICENSED. Two reasons: (1) pub.dev requires a recognized open-source
license for publish; (2) it aligns with willink-claude-kit's MIT path —
Phase 8 OSS化判断の Flutter 側を前倒しで決めた形。React 側の license 移行は
Phase 8 で別途判断する。
Added #
WillinkTheme.fromBrand(WillinkBrand)factory + three named aliases (willink(),clublink(),fitai()).WillinkBrandenum (3 brands) withtoColorScheme().WillinkPrimitives— Dart constants mirroring@willink-labs/tokens/src/primitive.json.WillinkSemantics— semantic mapping (default = i-Willink).WillinkBrandTokensThemeExtensionfor non-Material brand tokens (brandGlow,brandGradient,shadowSoft,shadowGlow).tokens_sync_test.dartenforcing parity with the canonical DTCG JSON (the same single-source-of-truth rule the React side has viacheck-tokens.test.ts).theme_data_test.dartcovering each brand's primary color andWillinkBrandTokenspresence. intentional exclusion).
Status notes #
- pub.dev publish requires CEO action (org account
willink_labssetup + Trusted Publisher configuration). The included.github/workflows/publish-flutter.ymlis workflow_dispatch-ready once that's in place. later adopts willink_theme independently, that's a separate downstream decision and would not require changes to this package. - Light mode only at this stage; dark variants will be added when one of the consumer apps adopts dark mode (Phase 6 candidate).