willink_theme 0.4.0
willink_theme: ^0.4.0 copied to clipboard
i-Willink Design System theme for Flutter — Material 3 ThemeData factories with brand axes (willink / clublink / fitai).
@willink-labs/flutter_theme (pub.dev: willink_theme) #
Material 3 theme for i-Willink Design System Flutter apps. Mirrors the React DS
[data-brand="willink|clublink"] pattern from @willink-labs/tailwind-preset.
Quick start #
import 'package:flutter/material.dart';
import 'package:willink_theme/willink_theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: WillinkTheme.clublink(), // or .willink() / .fitai()
home: const HomeScreen(),
);
}
}
Brand axes #
| Brand | Primary | Use case |
|---|---|---|
WillinkBrand.willink |
violet #7C3AED |
i-Willink default · marketing apps |
WillinkBrand.clublink |
blue #2563EB |
ClubLink (web) / clubhouse (Flutter, same brand) |
WillinkBrand.fitai |
blue #3B82F6 + emerald |
fit-ai (existing palette preserved) |
tsuu (delivered to piu Inc.) is intentionally out of scope. If piu adopts
this package independently, that is their choice — they would either reuse one
of the brands above or propose a new axis through the maintainers.
Why a Theme package and not just constants? #
- A consumer that wires
WillinkTheme.fromBrand(brand)intoMaterialApp.themegets the complete Material 3 stack (ColorScheme, FilledButton shape, InputDecoration radius, Card shape, Divider, etc.) in one line. No manual re-implementation per app. - Brand-specific tokens that don't fit Material's palette (glow color,
hero gradient, soft shadows) are carried as a
WillinkBrandTokensThemeExtension, accessible from any widget viaTheme.of(context).extension<WillinkBrandTokens>()!.
Spacing #
Material 3 4-multiple scale. Use these everywhere you'd otherwise hard-code
a double for padding / gap / margin:
Padding(padding: EdgeInsets.all(WillinkSpacing.md), child: ...)
SizedBox(height: WillinkSpacing.lg)
| Token | dp | Typical use |
|---|---|---|
WillinkSpacing.xs |
4 | icon ↔ label gap, dense rows |
WillinkSpacing.sm |
8 | chip gaps, compact layouts |
WillinkSpacing.md |
16 | default content padding |
WillinkSpacing.lg |
24 | section separator |
WillinkSpacing.xl |
32 | between page-level sections |
WillinkSpacing.xxl |
48 | hero blocks, top-of-page padding |
Brand-aware gradients #
WillinkBrandTokens (a ThemeExtension) exposes three gradient presets per
brand:
final tokens = Theme.of(context).extension<WillinkBrandTokens>()!;
Container(decoration: BoxDecoration(gradient: tokens.brandGradient)); // hero
Container(decoration: BoxDecoration(gradient: tokens.subtleGradient)); // bg
Container(decoration: BoxDecoration(gradient: tokens.aiGradient)); // AI moments
brandGradient rotates with the brand (i-Willink violet→blue, ClubLink
blue→green, fit-ai blue→emerald). subtleGradient and aiGradient are
brand-agnostic (white→brand-50→sky-50 / cyan→brand-500→pink).
Token sync #
WillinkPrimitives.* constants must match
@willink-labs/tokens/src/primitive.json. Verified by
test/tokens_sync_test.dart — CI fails if a hex value drifts.
This is the same "single source of truth" guarantee that vitest's
check-tokens.test.ts enforces on the React side. Both languages share one
canonical DTCG JSON.
Maintenance — adding a new brand #
- Add to
lib/src/tokens/primitive.dartif any new primitive shades are needed. - Extend
WillinkBrandenum inlib/src/brand_axis.dart(handle the new case intoColorScheme()). - Add a static getter on
WillinkBrandTokensfor the brand's non-Material extras. - Add
WillinkTheme.<newBrand>()alias inlib/src/theme_data.dart. - Update
test/brand_axis_test.dartenum count. - Bump version in
pubspec.yaml(minor for additive, major for breaking).
Versioning #
0.x.x — pre-1.0, minor bumps may include breaking changes. Pin with ~0.1.0
for exact-minor stability. Released as part of the same lockstep cadence as
@willink-labs/{tokens,tailwind-preset,react}.
License #
MIT License — see LICENSE for details.