Mihr UI logoMihr UI

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:

main.dart
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

ParameterTypeDefaultDescription
brandColorScale?MihrColors.brandPrimary brand palette. Affects buttons, links, active states, and all brand tokens.
grayColorScale?MihrColors.gray / grayDarkNeutral palette. light() uses gray, dark() uses grayDark.
errorColorScale?MihrColors.errorError/destructive state colors.
warningColorScale?MihrColors.warningWarning state colors.
successColorScale?MihrColors.successSuccess state colors.
fontFamilyString'Inter'Font family applied to all text styles and the TextTheme.
buttonThemeMihrButtonThemeData?Untitled UI defaultsCustomizes 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:

moss
greenLight
green
teal
cyan
blueLight
blue
blueDark
indigo
violet
purple
fuchsia
pink
rose
orangeDark
orange
yellow
Dart
import '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:

Dart
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:

ValueLightness rangeBehavior
true0.25 – 0.60Shade 600 on white guaranteed ≥ 4.5:1 contrast. Best for most brand colors.
false0.25 – 0.95Keeps very light colors (pastels, rose-300). Use adaptive foreground text for contrast.
Dart
final pastel = ColorScaleGenerator.fromHex(
  '#FFB3BA',
  clampLightness: false,
);

WCAG validation

The generator enforces 8 WCAG 2.1 contrast rules automatically. Use fromColorWithReport to audit:

Dart
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:1

Semantic 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 tokens

primary, secondary, tertiary, placeholder, white, brandPrimary, brandSecondary, errorPrimary, warningPrimary, successPrimary

BackgroundColors31 tokens

primary, secondary, tertiary, active, overlay, brandSolid, brandSolidHover, brandSection, errorSolid, warningSolid, successSolid

BorderColors10 tokens

primary, secondary, tertiary, disabled, brand, brandAlt, error, errorSubtle

ForegroundColors20 tokens

primary, secondary, tertiary, white, disabled, brandPrimary, brandSecondary, errorPrimary, successPrimary


Button theme

MihrButtonThemeData customizes the appearance of all Mihr button variants globally:

PropertyTypeDefault
shapeOutlinedBorder?RoundedRectangleBorder (MihrRadius.borderMd)
shadowsMihrButtonShadows?MihrButtonShadows.standard
sizesMihrButtonSizes?SM 36px, MD 40px, LG 44px, XL 48px
linkSizesMihrLinkButtonSizes?SM 20px, MD 20px, LG 24px, XL 24px
primaryStyleButtonStyle?null (uses defaults)
secondaryStyleButtonStyle?null
tertiaryStyleButtonStyle?null
destructiveStyleButtonStyle?null
custom_buttons.dart
MihrTheme.light(
  config: MihrThemeConfig(
    buttonTheme: MihrButtonThemeData(
      // Rounder buttons
      shape: RoundedRectangleBorder(
        borderRadius: MihrRadius.borderXl, // 12px
      ),
      // Flat look — no shadows
      shadows: MihrButtonShadows.flat,
    ),
  ),
);

Shadow presets:

PresetDescription
MihrButtonShadows.standardXS outer shadow + inner ring + focus ring (default)
MihrButtonShadows.flatNo shadows at all
MihrButtonShadows.subtleLight 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.

TokenValuePixels
none00px
xxs0.125rem2px
xs0.25rem4px
sm0.375rem6px
md0.5rem8px
lg0.75rem12px
xl1rem16px
x2l1.25rem20px
x3l1.5rem24px
x4l2rem32px
x5l2.5rem40px
x6l3rem48px
x7l4rem64px
x8l5rem80px
x9l6rem96px
x10l8rem128px
x11l10rem160px

Convenience helpers:

Dart
// 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.

BreakpointMin widthColumnsGutter
mobile0px616px
tablet768px832px
desktop1280px1232px
Dart
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 16

Container widths

MihrWidths provides max-width tokens for constraining content areas, plus container padding and paragraph width helpers.

TokenMax width
xxs320px
xs384px
sm480px
md560px
lg640px
xl768px
x2l1024px
x3l1280px
x4l1440px
x5l1600px
x6l1920px

Container helpers:

TokenValueDescription
containerPaddingMobile16pxHorizontal padding on mobile
containerPaddingDesktop32pxHorizontal padding on desktop
containerMaxWidth1280pxMain content container max width
paragraphMaxWidth720pxOptimal reading width for text
Dart
ConstrainedBox(
  constraints: MihrWidths.constraintsContainer,
  child: Padding(
    padding: EdgeInsets.symmetric(
      horizontal: MihrWidths.containerPaddingDesktop,
    ),
    child: content,
  ),
);