Skip to content

Theming ​

CDX UI is themed through a single ThemeExtension — CdxConfig — and a factory function — createCdxTheme — that produces a ThemeData with every Material component theme pre-wired. Apps customize tokens once; every CDX widget picks them up automatically.

Live Example ​

Open the full route: Theming.

CdxConfig ​

CdxConfig is registered as a ThemeData.extensions entry. Resolve it anywhere via:

dart
final cdx = CdxConfig.of(context);          // never null — falls back to defaults
final maybeCdx = CdxConfig.maybeOf(context); // null if not registered

Presets: Blue and Classic ​

CDX ships two looks, each a CdxConfig preset that takes overrides of any token:

  • The Blue design system ("Blue"), CdxConfig.blue(), is CDX's current look: labels above 36 px soft-outlined fields, every control at the same 36 px, the Blue type scale (CdxTypeScale.blue(), 14/20 body on a 4 px baseline), Blue field style (CdxFieldStyle.blue()), overlays at a 6 px radius, and dialogs and panels on the lightest surface.
  • Classic, CdxConfig() or CdxConfig.classic(), is the older CDX look, kept for apps built on it.

An app picks a preset and customizes only its brand (colours, font); the controls take everything else from the preset.

dart
createCdxTheme(
  colorScheme: scheme,
  textTheme: textTheme,
  config: const CdxConfig.blue(primary: MyBrand.primary),
);

Shape tokens ​

TokenTypeDefaultUsed by
controlBorderRadiusBorderRadius4Badges, chips, dropdown frames, dialog actions
containerBorderRadiusBorderRadius8Cards, dialogs, panels, banners
controlPaddingdouble4Badge padding, chip padding (multiplied)
containerPaddingdouble16SectionCard, banner, locked surface
controlHeightdouble36Button, CdxSegmentedTabs, CdxMenu triggers
horizontalGapdouble16Dialog footer actions, toolbars, header action rows
verticalGapdouble16Form groups, settings rows, stacked section rhythm
compactControlSizedouble32Button.icon(size: CdxControlSize.compact), CdxChip(size: compact)
iconSizedouble16In-control icons across buttons, menus, segmented tabs, triggers
headerHeightdouble70Entity / panel / dialog header bands

Color tokens ​

Every color is nullable — null means "use the Material ColorScheme fallback". Resolvers are colocated with the field so call sites read from one place.

ResolverFalls back to
primaryOf(scheme)scheme.primary
errorOf(scheme)scheme.error
surfaceOf(scheme)scheme.surface
borderOf(scheme)scheme.outline
disabledOf(scheme)scheme.onSurface @ 38%
disabledButtonFillOf(scheme)scheme.onSurface @ 12% (M3 spec)
disabledButtonTextOf(scheme)scheme.onSurface @ 38% (M3 spec)
hoverTintOf(scheme)primary @ 8%
selectedTintOf(scheme)primary @ 16%

Status palette (Material does NOT ship this) ​

CdxConfig adds three brightness-aware palettes Material 3 lacks: success, warning, neutral. critical and info resolve through error and primary respectively (same canonical hex). All container/on-container pairs target ≥7:1 contrast (AAA).

dart
final cdx = CdxConfig.of(context);
final scheme = Theme.of(context).colorScheme;

cdx.successOf(scheme);          // Green-700 light / Emerald-400 dark
cdx.successContainerOf(scheme); // Green-100 light / Emerald-900 dark
cdx.warningOf(scheme);          // Amber-500 light / Amber-400 dark
cdx.criticalOf(scheme);         // == errorOf
cdx.infoOf(scheme);             // == primaryOf
cdx.neutralOf(scheme);          // Slate-600 light / Slate-400 dark

Used by Banner, StatusBadge, ActivityTimeline (ActivityOutcome), the workflow timeline, and any widget that needs status semantics.

Custom theme ​

dart
final theme = ThemeData.from(colorScheme: ColorScheme.fromSeed(...))
    .copyWith(extensions: [
  CdxConfig(
    controlHeight: 36,                          // tighter buttons
    selectedTint: Colors.indigo.withValues(alpha: 0.18),
    warning: const Color(0xffd97706),            // brand-tuned amber
  ),
]);

Or wire everything at once with createCdxTheme.

createCdxTheme ​

createCdxTheme returns a ThemeData with every Material component theme pre-wired (buttons, dialog, card, snackbar, picker, chip, slider, divider, navigation rail, tab bar, etc.) plus the CdxConfig extension. Brands typically call it once per brightness:

dart
final lightTheme = createCdxTheme(
  colorScheme: brand.lightScheme,
  textTheme: brand.textTheme,
);

final darkTheme = createCdxTheme(
  colorScheme: brand.darkScheme,
  textTheme: brand.textTheme,
);

// Tune sizing if needed:
final dense = createCdxTheme(
  colorScheme: brand.lightScheme,
  textTheme: brand.textTheme,
  config: const CdxConfig(controlHeight: 36, compactControlSize: 28),
);

The component themes pulled in by createCdxTheme cover:

  • Buttons (elevated, filled, outlined, text, icon, floating, menu)
  • Inputs (inputDecorationTheme, dropdownMenuTheme, searchBarTheme, searchViewTheme)
  • Containers (cardTheme, dialogTheme, appBarTheme, navigationBarTheme, navigationRailTheme, tabBarTheme)
  • Form controls (switchTheme, checkboxTheme, radioTheme, sliderTheme, chipTheme)
  • Data (dataTableTheme, segmentedButtonTheme, listTileTheme)
  • Pickers (datePickerTheme, timePickerTheme)
  • Menus & overlays (popupMenuTheme, menuTheme, tooltipTheme, snackBarTheme)
  • Misc (dividerTheme, progressIndicatorTheme)

CdxInverseTheme ​

A dark bar on a light page (a selection bar, a floating action strip) is the app's inverse surface. Wrap what it holds in CdxInverseTheme: inside, the scheme is turned inside out (the outer inverseSurface is the surface, onInverseSurface the onSurface, inversePrimary the primary, the brightness flipped) and every component theme is rebuilt from it, so a Button.icon, a CdxLink or a plain Text reads without a colour of its own. The app's CdxConfig carries through (radii, sizes, field style, type scale) without its colour overrides (CdxConfig.withoutColors()).

It paints nothing; the host draws the surface:

dart
DecoratedBox(
  decoration: BoxDecoration(
    color: Theme.of(context).colorScheme.inverseSurface,
    borderRadius: CdxConfig.of(context).controlBorderRadius,
  ),
  child: CdxInverseTheme(child: Row(children: actions)),
)

CdxInverseTheme.of(context) returns the same ThemeData, for a route or overlay opened from the bar. Inverse themes are cached, so a rebuilt bar never rebuilds the component themes.

  • Density & Sizing — controlHeight, compactControlSize, headerHeight
  • Overlays — hoverTint, selectedTint, CdxStateLayer
  • Buttons — read controlHeight and controlBorderRadius
  • Badges — StatusBadge reads the status palette