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

2 min read

Tailwind

Tailwind v4's @theme block is for build-time tokens. djust's tokens live at runtime and change per request. This page shows the brand-alias pattern — how to declare Tailwind tokens that resolve through djust's runtime variables, so bg-brand-rust is generated at build time but its value still flows from whichever pack is active when the page loads.

The pattern

/* static/css/input.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";

@theme {
    --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;

    /* Brand aliases — Tailwind generates utilities at compile time;
     * the value resolves through djust's runtime CSS variable, so
     * swapping the active pack repaints the site without rebuilding
     * Tailwind. */
    --color-brand-dark:   hsl(var(--background));
    --color-brand-panel:  hsl(var(--card));
    --color-brand-rust:   hsl(var(--primary));
    --color-brand-text:   hsl(var(--foreground));
    --color-brand-muted:  hsl(var(--muted-foreground));
    --color-brand-border: hsl(var(--border));
}

@source "../../templates";
@source "../../docs_app";

After make tailwind-build, you can use:

<button class="bg-brand-rust text-brand-text hover:bg-brand-rust/80">
    Read the docs
</button>

The class names are static (Tailwind sees them at build time and emits the rules). The colors are dynamic (the rules reference the runtime --background etc., which swap per request).

Why two layers?

Tailwind v4 generates utilities from @theme declarations. If you fed the runtime var(--background) directly into @theme, Tailwind would freeze its value at build time and you'd lose the runtime swap.

The brand aliases are an indirection — they live at build time (so Tailwind can generate utilities) but they contain runtime references (so the actual paint color is dynamic).

Naming conventions

Two reasonable strategies:

1. Mirror djust's vocabulary

--color-primary:           hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-card:              hsl(var(--card));

Pro: zero translation cost. Con: Tailwind already has text-primary semantics; collisions are easy.

2. Use a brand prefix

--color-brand-dark:   hsl(var(--background));
--color-brand-rust:   hsl(var(--primary));
--color-brand-text:   hsl(var(--foreground));
--color-brand-muted:  hsl(var(--muted-foreground));
--color-brand-border: hsl(var(--border));

Pro: no Tailwind collisions, easy to grep, semantic at a glance. Con: an extra layer of name to maintain.

This is the docs.djust.org convention and what most shipped sites use.

Opacity composition

Tailwind's /N opacity modifier works through the alias because the runtime variable is the HSL triplet:

<div class="bg-brand-rust/20"></div>     <!-- 20% opacity rust -->
<div class="border-brand-border/40"></div>

Internally Tailwind generates background: hsl(var(--primary) / 0.2), which composes with djust's HSL-triplet convention.

What about @apply?

Avoid @apply for brand utilities — they bake in the build-time alias, which still respects runtime swaps but adds an indirection that makes later debugging harder. Inline the utility classes on the elements instead, or write plain CSS that uses var(--...) directly.

What about other CSS frameworks?

Bootstrap users follow the same pattern via Sass variables. djust ships a bootstrap-bridge.css that maps shadcn tokens to Bootstrap variables; see CSS Frameworks.

See also

  • Tokens — what each --background, --primary, --foreground actually paints.
  • CSS Frameworks — the broader framework-abstraction story (Bootstrap 4 / 5, Tailwind, plain) plus the Bootstrap bridge that does for .btn-primary what brand aliases do for Tailwind.
  • CSS Frameworks (setup guide) — full Tailwind / Bootstrap installation guide with djust.
  • Recipes — patterns for overriding tokens per-page or per-section without a new pack.

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