Skip to content
djust/docs
Appearance
Mode
djust.org →
Browse documentation

3 min read

Authoring a Pack

A theme pack is a Python module that exports three objects: a PRESET (colors, light + dark), a DESIGN_SYSTEM (typography / layout / animation / interaction), and a PACK (the cohesive bundle). Register all three and your pack is selectable from settings, the theme panel, and the dev panel.

Minimum viable example

# my_app/themes/sunset.py
from djust.theming import (
    AnimationStyle, ColorScale, DesignSystem, IconStyle, InteractionStyle,
    LayoutStyle, SurfaceStyle, ThemePack, ThemePreset, ThemeTokens,
    TypographyStyle, PATTERN_MINIMAL, ILLUST_LINE,
)

# ─── colors ──────────────────────────────────────────────────────────
DARK = ThemeTokens(
    background=ColorScale(20, 80, 8),         # warm near-black
    foreground=ColorScale(30, 30, 95),
    card=ColorScale(20, 80, 11),
    card_foreground=ColorScale(30, 30, 95),
    popover=ColorScale(20, 80, 11),
    popover_foreground=ColorScale(30, 30, 95),
    primary=ColorScale(15, 90, 60),           # sunset orange
    primary_foreground=ColorScale(20, 80, 8),
    secondary=ColorScale(20, 80, 14),
    secondary_foreground=ColorScale(30, 30, 95),
    muted=ColorScale(20, 50, 15),
    muted_foreground=ColorScale(20, 30, 60),
    accent=ColorScale(15, 90, 60),
    accent_foreground=ColorScale(20, 80, 8),
    destructive=ColorScale(355, 75, 55),
    destructive_foreground=ColorScale(0, 0, 100),
    success=ColorScale(160, 60, 45),
    success_foreground=ColorScale(0, 0, 100),
    warning=ColorScale(40, 90, 55),
    warning_foreground=ColorScale(0, 0, 8),
    info=ColorScale(210, 60, 55),
    info_foreground=ColorScale(0, 0, 100),
    link=ColorScale(15, 90, 60),
    link_hover=ColorScale(15, 90, 70),
    code=ColorScale(20, 80, 14),
    code_foreground=ColorScale(30, 30, 95),
    selection=ColorScale(15, 80, 55),
    selection_foreground=ColorScale(20, 80, 8),
    brand=ColorScale(15, 90, 60),
    brand_foreground=ColorScale(20, 80, 8),
    border=ColorScale(0, 0, 18),
    input=ColorScale(0, 0, 18),
    ring=ColorScale(15, 90, 60),
    surface_1=ColorScale(20, 80, 8),
    surface_2=ColorScale(20, 80, 11),
    surface_3=ColorScale(20, 80, 14),
)

LIGHT = ThemeTokens(
    background=ColorScale(30, 100, 98),       # warm cream paper
    foreground=ColorScale(20, 80, 8),
    # …same shape, light values
)

PRESET = ThemePreset(
    name='sunset',
    display_name='Sunset',
    description='Warm dusk gradients on a near-black base',
    light=LIGHT,
    dark=DARK,
    default_mode='dark',
    radius=0.5,
)

# ─── design system (typography / layout / etc.) ──────────────────────
DESIGN_SYSTEM = DesignSystem(
    name='sunset',
    display_name='Sunset',
    description='Wide-tracking display headings, monospace labels',
    category='editorial',
    typography=TypographyStyle(
        name='sunset',
        font_family_sans="'Inter', system-ui, sans-serif",
        font_family_mono="'JetBrains Mono', monospace",
        scale_ratio=1.25,
    ),
    layout=LayoutStyle(name='sunset', density='comfortable', spacing_unit=4),
    surface=SurfaceStyle(name='sunset', shadow_intensity='subtle', border_radius='medium'),
    icons=IconStyle(name='sunset', style='outlined', stroke_width='1.5'),
    animation=AnimationStyle(name='sunset', transition_style='smooth', duration_normal='0.25s'),
    interaction=InteractionStyle(name='sunset', button_hover='color', focus_style='ring'),
)

# ─── pack (the cohesive bundle) ──────────────────────────────────────
PACK = ThemePack(
    name='sunset',
    display_name='Sunset',
    description='Warm dusk preset on an editorial design system',
    category='editorial',
    design_theme='sunset',
    color_preset='sunset',
    icon_style=DESIGN_SYSTEM.icons,
    animation_style=DESIGN_SYSTEM.animation,
    pattern_style=PATTERN_MINIMAL,
    interaction_style=DESIGN_SYSTEM.interaction,
    illustration_style=ILLUST_LINE,
)

Registration

Register all three during app startup:

# my_app/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        from djust.theming import register_pack, register_preset, register_design_system
        from .themes.sunset import PRESET, DESIGN_SYSTEM, PACK

        register_preset(PRESET)
        register_design_system(DESIGN_SYSTEM)
        register_pack(PACK)

Then point your settings at it:

LIVEVIEW_CONFIG = {
    'theme': {'pack': 'sunset', 'default_mode': 'dark'},
}

Mixing and matching

You don't need a custom design system to ship a custom pack. You can build on a shipped one:

from djust.theming import get_registry

# Pair your custom preset with the shipped 'editorial' design system.
editorial_ds = get_registry().get_design_system('editorial')

PACK = ThemePack(
    name='sunset-editorial',
    display_name='Sunset Editorial',
    description='Sunset colors with the editorial design system',
    category='editorial',
    design_theme='editorial',
    color_preset='sunset',
    icon_style=editorial_ds.icons,
    animation_style=editorial_ds.animation,
    pattern_style=PATTERN_MINIMAL,
    interaction_style=editorial_ds.interaction,
    illustration_style=ILLUST_LINE,
)

This is how the shipped packs are structured — many of them share design systems and only differ in their PRESET.

Field reference

ThemePreset

The full set is in _types.py, but the load-bearing fields:

FieldRequiredWhat it does
nameyesUnique identifier used in settings and the API.
display_nameyesHuman-readable name shown in the theme panel.
light / darkyesThemeTokens for each mode. Both required.
default_modeno'light' or 'dark'. Determines which set goes into :root. Default: 'light'.
radiusnoBorder-radius multiplier in rem. Default: 0.5.
extra_css_varsnoProject-specific knobs. See extra_css_vars.
extra_css_vars_light / _darknoMode-specific overrides.
surfacenoSurfaceTreatment for glass / gradient / noise effects.

ThemeTokens

22 color slots — see Tokens for the full table and what each one paints. Every slot is a ColorScale(hue, saturation, lightness) with HSL values 0-360 / 0-100% / 0-100%.

DesignSystem

FieldWhat it does
typographyFont families, scale ratio, weight scale.
layoutDensity (compact / comfortable / spacious), spacing unit.
surfaceShadow intensity, border-radius preset, surface treatment.
iconsStyle (outlined / filled / rounded), stroke width, corner rounding.
animationEntrance / hover / loading effects, durations, easing.
interactionButton-hover, link-hover, card-hover, focus-style behaviors.

ThemePack

The cohesive bundle — references a preset name and a design-system name, plus pattern + illustration choices and copies of icon / animation / interaction styles for cross-pack querying.

Testing your pack

Local dev:

  1. Add the pack module + register it in apps.py:ready().
  2. Restart the dev server.
  3. Open /_djust/panel/ (DEBUG=True) → Theming tab → swap to your pack.
  4. Click through every page that matters; look for color leaks (see Troubleshooting).
  5. Toggle light / dark mode; verify both sets work.

See also

Spotted a typo or want to improve this page? Edit on GitHub →