mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter

Theming System#

Overview#

  • Many Material 3 components rely on the broader set of tone-based surfaces/containers and "onSurfaceVariant/outlineVariant" emphasis roles.
  • Material 3 is explicitly designed around color roles applied consistently across UI.
  • Flutter has been expanding Material 3 ColorScheme roles (tone surfaces + containers, and more accent roles), and some older roles are deprecated (e.g., background/onBackground, surfaceVariant).

Architecture#

Core Types#

ThemePack (lib/src/app/theming/theme_pack.dart)

  • Collection of theme variants (e.g., "Oxocarbon" pack with light/dark variants)
  • Contains metadata: id, name, author
  • Provides accessors: lightVariant, darkVariant, getVariant(id)

ThemeVariant (lib/src/app/theming/theme_variant.dart)

  • Single brightness variant with a Material 3 ColorScheme
  • Built from ThemeSpec palette definitions
  • Contains: id, brightness, derivedScheme

ThemeSpec (lib/src/app/theming/theme_spec.dart)

  • Palette definition for a theme variant
  • Maps raw colors to M3 ColorScheme roles
  • Defines: surfaces, containers, outlines, accents, typography, shapes

ThemeFactory (lib/src/app/theming/theme_factory.dart)

  • Builds complete ThemeData from ThemeVariant
  • Centralizes component themes (NavBar, Chips, Cards, ListTiles, Dividers, Inputs)
  • Ensures consistent M3 role usage across all components

Principles (Material 3)#

  • Use semantic color roles (surface containers, outlineVariant, onSurfaceVariant) rather than raw hex values sprinkled through widgets.
  • Create hierarchy with tone-based surfaces/containers rather than heavy dividers.
  • Navigation components must have a clear active indicator (selected state).
  • Chips have specific color roles in spec (filter chip uses onSurfaceVariant + outline and optionally surfaceContainerLow).

Material 3 Role Usage#

Surface Containers (Hierarchy)#

Tone-based surfaces create visual hierarchy without heavy elevation:

surfaceContainerLowest  ← dialogs, sheets (highest elevation)
surfaceContainerHigh    ← embeds, elevated cards
surfaceContainer        ← default containers
surfaceContainerLow     ← posts, cards (slightly elevated)
surface                 ← base background
surfaceDim/surfaceBright ← adaptive surface tones

Component Patterns#

  • Active indicator: uses container role (NOT primary tint overlay)
  • Selected icons/labels: onSurface
  • Unselected icons/labels: onSurfaceVariant

FilterChips (Feed selector)#

  • Unselected: outline + onSurfaceVariant
  • Selected: secondaryContainer + onSecondaryContainer

Cards (Posts)#

  • Use surfaceContainerLow for slight elevation from background
  • Embeds use surfaceContainerHigh (higher than parent post)

Dividers#

  • Default: outlineVariant (subtle)
  • Emphasized boundaries: outline

Text Emphasis#

  • Body text: onSurface (highest legibility)
  • Metadata (handle, timestamp): onSurfaceVariant

Creating Theme Packs#

1. Define Palette#

Map your palette to M3 surface/container/outline/accent roles:

final mySpec = ThemeSpec(
  // Base surfaces
  surface: Color(0xFF1A1A1A),
  surfaceDim: Color(0xFF121212),
  surfaceBright: Color(0xFF242424),

  // Container ladder (dark theme: lowest is darkest)
  surfaceContainerLowest: Color(0xFF0F0F0F),
  surfaceContainerLow: Color(0xFF1E1E1E),
  surfaceContainer: Color(0xFF242424),
  surfaceContainerHigh: Color(0xFF2E2E2E),
  surfaceContainerHighest: Color(0xFF393939),

  // Outlines
  outline: Color(0xFF525252),
  outlineVariant: Color(0xFF393939),

  // Text
  onSurface: Color(0xFFF2F4F8),
  onSurfaceVariant: Color(0xFFB0B0B0),

  // Accents
  primary: Color(0xFF0085FF),
  onPrimary: Color(0xFFFFFFFF),
  secondary: Color(0xFF78A9FF),
  onSecondary: Color(0xFF000000),
  // ... other accent roles
);

2. Create Variants#

final darkVariant = ThemeVariant(
  id: 'mypack-dark',
  brightness: Brightness.dark,
  spec: myDarkSpec,
);

final lightVariant = ThemeVariant(
  id: 'mypack-light',
  brightness: Brightness.light,
  spec: myLightSpec,
);

3. Build ThemePack#

final myPack = ThemePack(
  id: 'mypack',
  name: 'My Pack',
  author: 'Me',
  variants: [darkVariant, lightVariant],
);

4. Validate with Role Lint Tests#

Role lint checks ensure:

  • All required M3 roles are defined
  • Hierarchy integrity (surface != surfaceContainerLow, etc.)
  • Container ladder ordering matches brightness (dark: lowest is darkest, light: lowest is lightest)
  • Emphasis differentiation (onSurface != onSurfaceVariant)

Accent Strategy by Theme Type#

High-contrast themes (Oxocarbon, One Dark)#

  • Use vivid primary colors
  • Strong secondary/tertiary for highlights

Pastel themes (Catppuccin, Rosé Pine, Nord)#

  • Keep accents restrained
  • Use secondary for chips, tertiary for highlights
  • Avoid overwhelming soft palettes with saturated accents