Theming
Mihr UI's theming system is built on Flutter's ThemeExtension API. Every color, typography style, spacing value, and component appearance is driven by tokens that automatically adapt to light mode, dark mode, and your brand color.
Theme configuration
MihrTheme.light() and MihrTheme.dark() return a fully configured ThemeData with all semantic tokens, component themes, and typography pre-registered. Every parameter is optional — defaults produce a production-ready purple brand theme:
MaterialApp(
theme: MihrTheme.light(
config: MihrThemeConfig(
brand: AccentColors.indigo,
error: MihrColors.error,
warning: MihrColors.warning,
success: MihrColors.success,
fontFamily: 'Inter',
buttonTheme: MihrButtonThemeData(
shape: RoundedRectangleBorder(
borderRadius: MihrRadius.borderXl,
),
),
),
),
darkTheme: MihrTheme.dark(
config: MihrThemeConfig(
brand: AccentColors.indigo,
),
),
themeMode: ThemeMode.system,
);All parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
brand | ColorScale? | MihrColors.brand | Primary brand palette. Affects buttons, links, active states, and all brand tokens. |
gray | ColorScale? | MihrColors.gray / grayDark | Neutral palette. light() uses gray, dark() uses grayDark. |
error | ColorScale? | MihrColors.error | Error/destructive state colors. |
warning | ColorScale? | MihrColors.warning | Warning state colors. |
success | ColorScale? | MihrColors.success | Success state colors. |
fontFamily | String | 'Inter' | Font family applied to all text styles and the TextTheme. |
buttonTheme | MihrButtonThemeData? | Untitled UI defaults | Customizes button shape, shadows, sizes, and per-variant styles. |
Accent palettes
Mihr UI ships with 17 hand-tuned accent palettes via AccentColors. Each is a full 12-shade ColorScale (25–950) ready to use as brand:
mossgreenLightgreentealcyanblueLightblueblueDarkindigovioletpurplefuchsiapinkroseorangeDarkorangeyellowimport 'package:mihr_ui/mihr_ui.dart';
MihrTheme.light(config: MihrThemeConfig(brand: AccentColors.teal));Generate from any color
ColorScaleGenerator creates a full 12-shade palette from any single color. The input is treated as shade 600 and the generator distributes lightness in both directions, applies subtle hue shifting, scales saturation per shade, and auto-corrects for WCAG contrast:
import 'package:mihr_ui/mihr_ui.dart';
// From a hex string
final palette = ColorScaleGenerator.fromHex('#E63946');
// From a Color object
final palette2 = ColorScaleGenerator.fromColor(Colors.teal);
MihrTheme.light(config: MihrThemeConfig(brand: palette));clampLightness
Controls how the anchor lightness is clamped before shade distribution. Default is true:
| Value | Lightness range | Behavior |
|---|---|---|
true | 0.25 – 0.60 | Shade 600 on white guaranteed ≥ 4.5:1 contrast. Best for most brand colors. |
false | 0.25 – 0.95 | Keeps very light colors (pastels, rose-300). Use adaptive foreground text for contrast. |
final pastel = ColorScaleGenerator.fromHex(
'#FFB3BA',
clampLightness: false,
);WCAG validation
The generator enforces 8 WCAG 2.1 contrast rules automatically. Use fromColorWithReport to audit:
final (palette, report) = ColorScaleGenerator.fromColorWithReport(
const Color(0xFFE63946),
);
print(report.allPassed); // true
print(report.failures); // [] if all pass
// Standalone helpers
ColorScaleGenerator.contrastRatio(colorA, colorB);
ColorScaleGenerator.meetsWcagAA(foreground, background); // ≥ 4.5:1
ColorScaleGenerator.meetsWcagAALarge(foreground, background); // ≥ 3:1Semantic color tokens
Each color extension has light and dark factories that accept the same ColorScale parameters as MihrTheme. When you change the brand parameter, every semantic token re-derives automatically. Key token groups:
TextColors23 tokensprimary, secondary, tertiary, placeholder, white, brandPrimary, brandSecondary, errorPrimary, warningPrimary, successPrimary
BackgroundColors31 tokensprimary, secondary, tertiary, active, overlay, brandSolid, brandSolidHover, brandSection, errorSolid, warningSolid, successSolid
BorderColors10 tokensprimary, secondary, tertiary, disabled, brand, brandAlt, error, errorSubtle
ForegroundColors20 tokensprimary, secondary, tertiary, white, disabled, brandPrimary, brandSecondary, errorPrimary, successPrimary
Button theme
MihrButtonThemeData customizes the appearance of all Mihr button variants globally:
| Property | Type | Default |
|---|---|---|
shape | OutlinedBorder? | RoundedRectangleBorder (MihrRadius.borderMd) |
shadows | MihrButtonShadows? | MihrButtonShadows.standard |
sizes | MihrButtonSizes? | SM 36px, MD 40px, LG 44px, XL 48px |
linkSizes | MihrLinkButtonSizes? | SM 20px, MD 20px, LG 24px, XL 24px |
primaryStyle | ButtonStyle? | null (uses defaults) |
secondaryStyle | ButtonStyle? | null |
tertiaryStyle | ButtonStyle? | null |
destructiveStyle | ButtonStyle? | null |
MihrTheme.light(
config: MihrThemeConfig(
buttonTheme: MihrButtonThemeData(
// Rounder buttons
shape: RoundedRectangleBorder(
borderRadius: MihrRadius.borderXl, // 12px
),
// Flat look — no shadows
shadows: MihrButtonShadows.flat,
),
),
);Shadow presets:
| Preset | Description |
|---|---|
MihrButtonShadows.standard | XS outer shadow + inner ring + focus ring (default) |
MihrButtonShadows.flat | No shadows at all |
MihrButtonShadows.subtle | Light outer shadow only |
Spacing
MihrSpacing provides a consistent spacing scale built on a 4px grid. Use semantic tokens for component-level spacing, or primitives for pixel-precise control. Pre-built EdgeInsets and SizedBox gap helpers are also available.
| Token | Value | Pixels |
|---|---|---|
none | 0 | 0px |
xxs | 0.125rem | 2px |
xs | 0.25rem | 4px |
sm | 0.375rem | 6px |
md | 0.5rem | 8px |
lg | 0.75rem | 12px |
xl | 1rem | 16px |
x2l | 1.25rem | 20px |
x3l | 1.5rem | 24px |
x4l | 2rem | 32px |
x5l | 2.5rem | 40px |
x6l | 3rem | 48px |
x7l | 4rem | 64px |
x8l | 5rem | 80px |
x9l | 6rem | 96px |
x10l | 8rem | 128px |
x11l | 10rem | 160px |
Convenience helpers:
// EdgeInsets (all sides)
Padding(padding: MihrSpacing.insetsMd); // 8px all
// EdgeInsets (horizontal / vertical)
Padding(padding: MihrSpacing.insetsHXl); // 16px left+right
Padding(padding: MihrSpacing.insetsVLg); // 12px top+bottom
// SizedBox gaps
Column(children: [
widget1,
MihrSpacing.gapVMd, // 8px vertical gap
widget2,
]);Breakpoints
MihrBreakpoints defines three responsive breakpoints with matching grid configurations. Use the helper methods to check the current device category.
| Breakpoint | Min width | Columns | Gutter |
|---|---|---|---|
mobile | 0px | 6 | 16px |
tablet | 768px | 8 | 32px |
desktop | 1280px | 12 | 32px |
final width = MediaQuery.sizeOf(context).width;
if (MihrBreakpoints.isDesktop(width)) {
// 12-column layout
} else if (MihrBreakpoints.isTablet(width)) {
// 8-column layout
} else {
// 6-column mobile layout
}
// Or get the grid config directly
final grid = MihrBreakpoints.gridFor(width);
print(grid.columns); // 12, 8, or 6
print(grid.gutter); // 32 or 16Container widths
MihrWidths provides max-width tokens for constraining content areas, plus container padding and paragraph width helpers.
| Token | Max width |
|---|---|
xxs | 320px |
xs | 384px |
sm | 480px |
md | 560px |
lg | 640px |
xl | 768px |
x2l | 1024px |
x3l | 1280px |
x4l | 1440px |
x5l | 1600px |
x6l | 1920px |
Container helpers:
| Token | Value | Description |
|---|---|---|
containerPaddingMobile | 16px | Horizontal padding on mobile |
containerPaddingDesktop | 32px | Horizontal padding on desktop |
containerMaxWidth | 1280px | Main content container max width |
paragraphMaxWidth | 720px | Optimal reading width for text |
ConstrainedBox(
constraints: MihrWidths.constraintsContainer,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: MihrWidths.containerPaddingDesktop,
),
child: content,
),
);