Mob's design token system lets you control color, spacing, and typography across the entire app from one place. Tokens are resolved at render time — change the theme and every component updates automatically on the next render.

Token types

Semantic color tokens describe purpose rather than appearance:

TokenRoleDefault
:primaryMain action color:blue_500
:on_primaryText/icons on primary:white
:secondarySecondary action color:gray_600
:on_secondaryText/icons on secondary:white
:backgroundScreen background:gray_900
:on_backgroundText on background:gray_100
:surfaceCard / sheet background:gray_800
:surface_raisedElevated card background:gray_700
:on_surfaceText/icons on surface:gray_100
:mutedSecondary / placeholder text:gray_500
:errorError state color:red_500
:on_errorText/icons on error:white
:borderDividers and outlines:gray_700

Spacing tokens (multiplied by space_scale):

TokenBase value
:space_xs4
:space_sm8
:space_md16
:space_lg24
:space_xl32

Text size tokens (multiplied by type_scale):

TokenBase sp
:xs12
:sm14
:base16
:lg18
:xl20
:"2xl"24
:"3xl"30
:"4xl"36
:"5xl"48
:"6xl"60

Radius tokens:

TokenDefault
:radius_sm6
:radius_md10
:radius_lg16
:radius_pill100

Using tokens in components

Pass token atoms as prop values for color, spacing, radius, and text size props. The renderer resolves them at render time:

%{
  type: :box,
  props: %{
    background:    :surface,          # → active theme's surface color
    padding:       :space_md,         # → 16 × space_scale
    corner_radius: :radius_md,        # → theme's radius_md value
  },
  children: [
    %{type: :text, props: %{
      text:       "Title",
      text_size:  :xl,               # → 20.0 × type_scale
      text_color: :on_surface,       # → active theme's on_surface color
    }, children: []}
  ]
}

Named themes

Mob ships three built-in themes:

There are two ways to set a theme:

At startup — pass it to use Mob.App. This sets the theme before any screen mounts:

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

At runtime — call Mob.Theme.set/1 from mount/3 or any event handler. Use this when you want the user to be able to switch themes:

def mount(_params, _session, socket) do
  Mob.Theme.set(Mob.Theme.Obsidian)
  {:ok, Mob.Socket.assign(socket, :theme, :obsidian)}
end

def handle_info({:tap, :theme_citrus}, socket) do
  Mob.Theme.set(Mob.Theme.Citrus)
  {:noreply, Mob.Socket.assign(socket, :theme, :citrus)}
end

Mob.Theme.set/1 is global — it applies to all screens on the next render. If your app only ever uses one theme, the use Mob.App option is sufficient and you don't need to call Mob.Theme.set/1.

Overriding individual tokens

Pass a {module, overrides} tuple to customise a named theme:

use Mob.App, theme: {Mob.Theme.Obsidian, primary: :rose_500, radius_md: 14}

Building a theme from scratch

Pass a keyword list of overrides against the neutral base:

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

Any tokens not listed inherit from the default neutral base.

Switching themes at runtime

Call Mob.Theme.set/1 at any point. The next render will use the new theme:

# Switch to a named theme
Mob.Theme.set(Mob.Theme.Citrus)

# Override individual tokens on the current theme
Mob.Theme.set({Mob.Theme.Obsidian, primary: :violet_500})

# Override against the neutral base
Mob.Theme.set(primary: :pink_500, type_scale: 1.2)

# Use a pre-built struct
Mob.Theme.set(%Mob.Theme{primary: :teal_500, space_scale: 1.1})

This is useful for accessibility features (larger type, high-contrast), user-selected themes, or A/B testing.

Publishing a custom theme

A theme is any module that exports theme/0 :: Mob.Theme.t():

defmodule AcmeCorp.BrandTheme do
  def theme do
    %Mob.Theme{
      primary:    :blue_700,
      on_primary: :white,
      surface:    0xFFF5F0E8,   # exact ARGB hex also accepted
      ...
    }
  end
end

Publish it as a Hex package. Anyone can use it:

use Mob.App, theme: AcmeCorp.BrandTheme

Base palette

Token atoms that are not semantic theme tokens resolve through the built-in palette. The palette covers grays, blues, greens, reds, oranges, purples, teals, pinks, and more — all as name_weight atoms (e.g. :blue_500, :gray_200, :emerald_400).

You can also pass raw ARGB hex integers directly as prop values:

%{type: :text, props: %{text: "Hi", text_color: 0xFFFF5733}, children: []}

Use raw integers sparingly. Semantic tokens give you free dark-mode and theme switching.