Mob.Theme.Material3 (mob v0.6.26)

Copy Markdown View Source

Material 3 (Material You) theme — cross-platform-consistent design system based on Google's Material Design 3 token specification.

Where Mob's other themes (Light, Dark, Birch, Citrus, Obsidian) pick colors and let each platform render with its own conventions, this theme overrides platform defaults so iOS and Android apps look the same: same color tokens, same typography scale, same shape system, same elevation rules.

When to use this

Use Material 3 when cross-platform visual consistency matters more than feeling native on each platform. iOS apps using this theme will look material-y rather than Apple-y — buttons get material shapes, type uses the material type scale, elevation is tonal-color overlay rather than iOS-style shadow.

Use one of the platform-respectful themes (Light, Dark) when native feel matters more than cross-platform parity.

Source

Token values are transcribed from Google's published Material 3 spec at https://m3.material.io/styles. The Compose Material 3 library (androidx.compose.material3) is the reference implementation; this module mirrors its baseline color scheme + type scale + shape system.

Usage

defmodule MyApp.App do
  use Mob.App, theme: Mob.Theme.Material3
end

Override individual tokens:

use Mob.App, theme: {Mob.Theme.Material3, primary: 0xFFE91E63}

What this maps onto Mob.Theme today

Mob.Theme's struct is smaller than the full M3 token surface (M3 has tiered surface-container colors, an elevation scale, a 5-tier type scale, a shape scale). For Phase 1 we map M3 tokens onto the closest existing Mob.Theme fields and document the gaps below. Phase 2 (visual fidelity) extends Mob.Theme's struct with the missing fields and wires them through the renderer.

Today's mapping:

  • primary / on_primary — M3 primary (purple-ish baseline)
  • secondary / on_secondary — M3 secondary
  • background / on_background — M3 surface (light mode background)
  • surface / on_surface — M3 surface-container
  • surface_raised — M3 surface-container-high (one tier up)
  • muted — M3 on-surface-variant
  • border — M3 outline-variant
  • error / on_error — M3 error
  • radius_sm / _md / _lg / _pill — M3 shape scale extra-small, small, medium, full

Gaps (no field in Mob.Theme yet — Phase 2 work):

  • Tonal-color elevation levels (M3 uses surface-color-at-elevation rather than shadows; needs new struct field)
  • Surface-container-lowest / -low / -high / -highest tiers (currently collapsed to surface + surface_raised)
  • Type scale (M3 has display-{l,m,s}, headline-{l,m,s}, title-{l,m,s}, body-{l,m,s}, label-{l,m,s} — 15 type roles; Mob currently uses type_scale as a single multiplier)
  • Shape scale extra-large + extra-extra-large
  • Motion duration + easing tokens

This module returns the closest-fit translation today; the visual polish round will fill in the gaps.

Summary

Functions

M3-spec tonal-elevation color stops, indexed by level (0-5).

M3 shape-scale corner radii (dp).

Returns the compiled Material 3 light theme struct.

M3 type-scale role definitions.

Functions

elevation_color(int)

@spec elevation_color(0..5) :: non_neg_integer()

M3-spec tonal-elevation color stops, indexed by level (0-5).

Used by Phase 2 renderer integration to apply tonal overlay on surfaces — M3's analog to iOS shadow elevation. Each level adds more primary-color tint to the base surface.

Indexed by dp of "elevation": 0, 1, 3, 6, 8, 12. Returned as the resolved color stop (already-blended primary-tinted surface).

Phase 2: wire this into the renderer for <Card> / <Box> with material: :elevated so an elevated surface renders with the correct tonal overlay rather than a fake shadow.

shape(atom)

@spec shape(atom()) :: non_neg_integer()

M3 shape-scale corner radii (dp).

theme()

@spec theme() :: Mob.Theme.t()

Returns the compiled Material 3 light theme struct.

Dark variant lives in Mob.Theme.Material3.Dark.

type_role(atom)

@spec type_role(atom()) :: %{
  size: number(),
  line_height: number(),
  weight: non_neg_integer()
}

M3 type-scale role definitions.

Each role returns %{size: pt, line_height: pt, weight: integer}. Phase 2: extend the renderer to accept a text_role: prop on Text components that resolves through this table.