Mob.Theme (mob v0.7.25)

Copy Markdown View Source

Design token system for Mob apps.

A theme is a compiled %Mob.Theme{} struct — a flat map of semantic tokens for colors, spacing, radii, and scale factors. The renderer resolves these tokens at render time so every component picks up the active theme automatically.

Using a named theme

Named themes are plain modules that export theme/0. Pass the module to use Mob.App:

use Mob.App, theme: MobThemes.Obsidian   # (the mob_themes style package)

Override individual tokens without leaving the theme:

use Mob.App, theme: {MobThemes.Obsidian, primary: :rose_500}

Anyone can publish a theme as a Hex package — any module with theme/0 returning a Mob.Theme.t() works:

use Mob.App, theme: AcmeCorp.BrandTheme

Building a theme from scratch

Pass a keyword list of overrides against the neutral base:

use Mob.App, theme: [primary: :emerald_500, type_scale: 1.1]

Or change the theme at runtime (e.g. for accessibility or user preference):

Mob.Theme.set(MobThemes.Obsidian)
Mob.Theme.set({MobThemes.Obsidian, type_scale: 1.2})
Mob.Theme.set(primary: :pink_500)

Base theme

When no theme is set the renderer uses the neutral base — plain dark grays with a standard blue primary. Functional, not opinionated. Good enough for hello world; swap in a named theme when you want personality.

Token reference

Semantic color tokens

:primary         main action colour          (default :blue_500)
:on_primary      text/icons on primary        (default :white)
:secondary       secondary action colour      (default :gray_600)
:on_secondary    text/icons on secondary      (default :white)
:background      page/screen background       (default :gray_900)
:on_background   text on background           (default :gray_100)
:surface         card / sheet background      (default :gray_800)
:surface_raised  elevated card background     (default :gray_700)
:on_surface      text/icons on surface        (default :gray_100)
:muted           secondary/placeholder text   (default :gray_500)
:error           error state colour           (default :red_500)
:on_error        text/icons on error          (default :white)
:border          dividers and outlines        (default :gray_700)

Spacing tokens (scaled by space_scale)

:space_xs    4 × scale
:space_sm    8 × scale
:space_md   16 × scale
:space_lg   24 × scale
:space_xl   32 × scale

Radius tokens

:radius_sm    theme.radius_sm   (default  6)
:radius_md    theme.radius_md   (default 10)
:radius_lg    theme.radius_lg   (default 16)
:radius_pill  theme.radius_pill (default 100)

Scale factors

type_scale:  1.0  # multiply all text sizes by this
space_scale: 1.0  # multiply all spacing tokens by this

Font tokens

fonts is a name → value map, resolved by the renderer exactly like colors (font: :heading walks this map, same two-step shape as text_color: :primary). :default is special — set it and the renderer injects it automatically onto any node that doesn't specify its own font:, so the whole app picks up a custom font without repeating it everywhere:

Mob.Theme.set(
  fonts: %{
    default: Mob.Theme.font("Inter-Regular", from_file: "priv/fonts/Inter-Regular.ttf"),
    heading: Mob.Theme.font("Inter-Bold", from_file: "priv/fonts/Inter-Bold.ttf")
  }
)

# in render/1:
%{type: :text, props: %{text: "Section", font: :heading}, children: []}
%{type: :text, props: %{text: "Body copy"}, children: []}  # gets :default automatically

font_fallback is an ordered list of font names tried, in order, if the resolved font can't be loaded on-device. Empty by default (the platform's own system-font fallback still applies) — set it when you want an explicit intermediate fallback before that.

A font value is either built with Mob.Theme.font/2 (recommended — it computes the Android name from the actual bundled file) or a bare string used as-is on both platforms.

Summary

Functions

Build a theme from a keyword list of overrides against the neutral base.

Returns the current OS appearance: :light or :dark.

Return the currently active theme (or the neutral base if none is set).

Return the neutral base theme.

Builds a font token value: the iOS PostScript name as given, paired with the Android resource name computed from the bundled file via Mob.Font.android_resource_name/1 — the same function mob_dev's asset planner uses when it copies the file into res/font/. One computation, used on both the build side and the theme side, so the two can't drift apart the way a hand-typed Android name could.

Returns the active theme's palette resolved to ARGB integers — semantic tokens (:primary, :on_surface, …) walked through the theme's color map and then through Mob.Renderer.colors/0. Used to push concrete values to the native side (Mob.Theme.set/1 does this automatically; callers usually don't need to invoke this directly).

Set the active theme. Accepts

Types

color_value()

@type color_value() :: atom() | non_neg_integer()

font_spec()

@type font_spec() :: %{optional(:ios) => String.t(), optional(:android) => String.t()}

font_value()

@type font_value() :: font_spec() | String.t()

t()

@type t() :: %Mob.Theme{
  background: term(),
  border: term(),
  error: term(),
  font_fallback: term(),
  fonts: term(),
  glass: term(),
  muted: term(),
  on_background: term(),
  on_error: term(),
  on_primary: term(),
  on_secondary: term(),
  on_surface: term(),
  primary: term(),
  radius_lg: term(),
  radius_md: term(),
  radius_pill: term(),
  radius_sm: term(),
  secondary: term(),
  space_scale: term(),
  surface: term(),
  surface_raised: term(),
  type_scale: term()
}

Functions

build(overrides \\ [])

@spec build(keyword()) :: t()

Build a theme from a keyword list of overrides against the neutral base.

Mob.Theme.build(primary: :emerald_500, type_scale: 1.1)

color_scheme()

@spec color_scheme() :: :light | :dark

Returns the current OS appearance: :light or :dark.

Reads from the platform NIF (UITraitCollection.userInterfaceStyle on iOS, Configuration.uiMode & UI_MODE_NIGHT_MASK on Android). Falls back to :light when running on the host BEAM (no NIF loaded), on platforms that don't expose appearance, or on legacy Android apps that haven't added MobBridge.getColorScheme() yet.

current()

@spec current() :: t()

Return the currently active theme (or the neutral base if none is set).

default()

@spec default() :: t()

Return the neutral base theme.

font(ios_family, opts)

@spec font(String.t(), [{:from_file, Path.t()}]) :: font_spec()

Builds a font token value: the iOS PostScript name as given, paired with the Android resource name computed from the bundled file via Mob.Font.android_resource_name/1 — the same function mob_dev's asset planner uses when it copies the file into res/font/. One computation, used on both the build side and the theme side, so the two can't drift apart the way a hand-typed Android name could.

fonts: %{
  heading: Mob.Theme.font("Inter-Bold", from_file: "priv/fonts/Inter-Bold.ttf")
}

resolved_palette(theme \\ current())

@spec resolved_palette(t()) :: %{required(atom()) => non_neg_integer()}

Returns the active theme's palette resolved to ARGB integers — semantic tokens (:primary, :on_surface, …) walked through the theme's color map and then through Mob.Renderer.colors/0. Used to push concrete values to the native side (Mob.Theme.set/1 does this automatically; callers usually don't need to invoke this directly).

set(theme)

@spec set(t() | module() | {module(), keyword()} | keyword()) :: :ok

Set the active theme. Accepts:

  • A compiled %Mob.Theme{} struct
  • A theme module (any module exporting theme/0, e.g. MobThemes.Obsidian)
  • A {module, overrides} tuple
  • A keyword list of overrides against the neutral base