website/docs: design language and CSS architecture documentation (#23343)

website/docs: add design language and CSS architecture documentation

Documents the design language for brand operators and the CSS/token
architecture for contributors:

- customize/branding/design-language.mdx — the token tiers, naming
  rules, and the package outputs a brand operator can consume.
- developer-docs/frontend/cascade-layers.md — the layer order, how rules
  are assigned to layers, and how the document and shadow-root cascades
  relate.
- developer-docs/frontend/css-architecture.md — the theme package, the
  token pipeline, and the PatternFly bridge.

custom-css.mdx now steers readers toward the semantic `--ak-*` tokens
and treats `--ak-c-*` and `--pf-*` as escape hatches.

Both developer pages were originally written as proposals, ahead of the
work they described. Cascade layers shipped in #23366 as
`@layer reset, vendor, components, theme, mode, brand`, not the order
those drafts proposed, and the token pipeline is Styleframe rather than
Style Dictionary. They now document the system as built, consistent with
web/src/styles/README.md.

The sidebar entry the original branch added is dropped: docs/sidebar.mjs
now autogenerates the Customize section, so the new page is picked up
from the directory without a manual entry.

Co-authored-by: Ken Sternberg <ken@goauthentik.io>
This commit is contained in:
Teffen Ellis
2026-07-31 04:05:59 +01:00
committed by GitHub
parent cf17b379c8
commit b048594c0a
5 changed files with 394 additions and 15 deletions

View File

@@ -27,6 +27,7 @@ CLDR
Cloudflare
DOTADIW
Docsmg
DTCG
GDTC
GHES
GHSA
@@ -69,6 +70,7 @@ TOTP
Transifex
Unenrollment
Unmigrate
USWDS
Wasabi
Wsfed
Xen

View File

@@ -16,7 +16,7 @@ Custom CSS is an advanced feature. Improper use may lead to unexpected behavior
## Concepts
authentik's design system is built around several key concepts that help manage and apply styles consistently. Understanding these concepts is essential for working with custom CSS effectively.
authentik's design system rests on a few concepts that keep styling consistent across the interface. Understanding them helps when writing custom CSS.
### Web components
@@ -30,7 +30,34 @@ Despite this encapsulation, many authentik web components are designed with them
CSS variables (also called CSS custom properties) define colors, spacing, typography, and other design elements throughout authentik. By overriding these variables in your custom CSS, you can change the interface's look and feel at a high level without targeting individual elements.
There's no comprehensive documentation of authentik's CSS variables. However, authentik uses [Patternfly 4](https://pf4.patternfly.org/developer-resources/global-css-variables/) and [Patternfly 5](https://v5-archive.patternfly.org/developer-resources/global-css-variables/) as its underlying design systems, which provide extensive documentation on their CSS variables.
Start with authentik's semantic `--ak-*` variables. They are shorter and more stable than the underlying PatternFly compatibility variables. PatternFly variables remain available for advanced overrides, but `--pf-*` names are implementation details and may change as authentik's UI evolves.
Common semantic variables include:
| Variable | Purpose |
| -------------------------- | ---------------------------------------- |
| `--ak-color-accent` | authentik brand accent |
| `--ak-color-primary` | primary action and highlight color |
| `--ak-color-text` | default text color |
| `--ak-color-text-muted` | secondary text color |
| `--ak-color-link` | link color |
| `--ak-color-surface` | default component surface |
| `--ak-color-surface-muted` | page or muted surface |
| `--ak-color-border` | default border color |
| `--ak-color-info` | informational state |
| `--ak-color-success` | success state |
| `--ak-color-warning` | warning state |
| `--ak-color-danger` | danger or destructive state |
| `--ak-space-md` | default spacing unit |
| `--ak-radius-sm` | default small radius |
| `--ak-shadow-md` | default drop shadow for cards and panels |
| `--ak-shadow-lg` | shadow for popovers and dropdowns |
| `--ak-font-weight-bold` | bold text weight |
| `--ak-z-index-lg` | z-index for popovers and dropdowns |
| `--ak-z-index-xl` | z-index for modal dialogs |
| `--ak-duration-normal` | default transition duration |
authentik currently bridges these semantic variables into selected PatternFly 4 variables. For example, changing `--ak-color-primary` also affects many PatternFly-backed buttons, links, and highlights.
### CSS parts
@@ -52,7 +79,7 @@ When customizing authentik's appearance, think of the theme as the primary way t
### Viewport considerations
When customizing styles that depend on viewport size, use [responsive design principles](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Responsive_Design). In authentik's design system, this means leveraging CSS variables that automatically adapt to viewport size rather than hardcoding styles for specific breakpoints.
When customizing styles that depend on viewport size, use [responsive design principles](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Responsive_Design). In authentik's design system, this means using CSS variables that adapt to viewport size rather than hardcoding styles for specific breakpoints.
```css title="❌ Static units"
ak-library::part(card-header-icon) {
@@ -64,7 +91,7 @@ ak-library::part(card-header-icon) {
```css title="✅ Responsive units"
ak-library::part(card-header-icon) {
font-size: 2rem;
margin-block-end: var(--pf-global--spacer--md);
margin-block-end: var(--ak-space-md);
}
```
@@ -78,7 +105,7 @@ Users may have accessibility preferences set in their operating system or browse
authentik's interface uses web standards APIs to detect **user agent hints** and determine the preferred visual experience. Where possible, authentik prioritizes system or browser-level preferences.
However, authentik also applies its own theming rules to ensure a consistent and visually appealing experience. This means authentik respects user preferences while maintaining a cohesive look.
However, authentik also applies its own theming rules, so the result stays consistent while still respecting user preferences.
For example:
@@ -100,22 +127,45 @@ This directly styles the `body` element based on the color scheme preference. Wh
```css title="✅ Theme-based styling"
:root {
--ak-global--Color--100: black;
--ak-global--BackgroundColor--100: white;
--ak-color-text: black;
--ak-color-surface: white;
}
html[data-theme="dark"] {
--ak-global--Color--100: white;
--ak-global--BackgroundColor--100: black;
--ak-color-text: white;
--ak-color-surface: black;
}
body {
background-color: var(--ak-global--BackgroundColor--100);
color: var(--ak-global--Color--100);
background-color: var(--ak-color-surface);
color: var(--ak-color-text);
}
```
Here, CSS variables define colors and backgrounds, with values adjusted based on the theme. This approach is more modular and maintainable—theme changes happen in one place without modifying individual element styles throughout the application.
Here, CSS variables define colors and backgrounds, with values adjusted based on the theme. This approach is more modular and maintainable. Theme changes happen in one place, without editing individual element styles across the application.
Prefer the semantic authentik variables for custom themes:
```css title="Custom brand colors"
:root {
--ak-color-accent: #e11d48;
--ak-color-primary: #2563eb;
--ak-color-link: #1d4ed8;
--ak-color-surface: #ffffff;
--ak-color-surface-muted: #f8fafc;
--ak-color-text: #0f172a;
--ak-color-border: #cbd5e1;
}
html[data-theme="dark"] {
--ak-color-primary: #60a5fa;
--ak-color-link: #93c5fd;
--ak-color-surface: #111827;
--ak-color-surface-muted: #030712;
--ak-color-text: #f9fafb;
--ak-color-border: #374151;
}
```
### Contrast ratios (`prefers-contrast`)
@@ -207,7 +257,7 @@ authentik doesn't require CSS parts or CSS variables. You can use standard CSS s
:::danger Your custom CSS may break after upgrading authentik
Direct element styling tends to break when authentik updates, since internal structures can change without noticeespecially for complex pages like the flow executor. CSS parts are more limited in scope but provide a stable theming API and a more predictable upgrade experience.
Direct element styling tends to break when authentik updates, since internal structures can change without notice, especially for complex pages like the flow executor. CSS parts are more limited in scope but provide a stable theming API and a more predictable upgrade experience.
:::
@@ -222,9 +272,9 @@ If you're still having trouble, the issue may stem from your CDN or reverse prox
### Where can I find documentation on available CSS Variables and Parts?
There's currently no comprehensive documentation on available CSS variables and parts in authentik. You can inspect the rendered HTML using browser developer tools to identify the CSS variables and parts applied to various elements.
authentik's public semantic variables are documented in the [CSS variables](#css-variables) section above. You can inspect the rendered HTML using browser developer tools to identify additional component-local CSS variables and parts applied to various elements.
Look for CSS variables prefixed with `--ak-` and `--pf-` to identify those defined by authentik and Patternfly respectively. HTML elements with the `part="..."` attribute indicate which parts are available for styling.
Prefer top-level semantic variables such as `--ak-color-primary`. Treat `--ak-c-*` and `--pf-*` variables as lower-level escape hatches unless a specific authentik release documents them as stable. HTML elements with the `part="..."` attribute indicate which parts are available for styling.
### The UI looks broken after upgrading authentik. What should I do?

View File

@@ -0,0 +1,115 @@
---
title: Design Language
slug: /brands/design-language
sidebar_label: Design Language
---
authentik's design language is the small set of stable conventions — colors, spacing, typography, motion, shadows, stacking — that every screen in the interface uses to render itself. Authoring those decisions in one place means a brand operator can override a handful of CSS variables and get a coherent result across every flow, dialog, and admin view, without targeting individual components.
This page describes the **what** and **why** of the design language. For practical "how do I change X" recipes, see [Custom CSS](./custom-css.mdx).
:::warning Advanced topic
Custom CSS is an advanced feature. Improper use may lead to unexpected behavior or visual issues. Test changes in a safe environment before applying them to production.
:::
## How the design system is organized
The system has three layers. The layer you target depends on how much of the interface you intend to affect.
| Layer | Example | What it controls | Recommended audience |
| -------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------- |
| **Semantic tokens** | `--ak-color-primary`, `--ak-space-md`, `--ak-shadow-lg` | The shared design decisions every component reads. Affects the whole interface. | Brand custom CSS, themes. |
| **Component-local tokens** | `--ak-c-login__main--BackgroundColor`, drawer width variables | One component's tunable knobs. | When a single component needs adjustment. |
| **CSS parts** | `ak-flow-executor::part(locale-select)` | Structural hooks a component intentionally exposes. | Targeted layout or visibility changes. |
Most brand customization should remain at the first layer. The lower layers are escape hatches.
## Token groups
The semantic surface consists of around 60 tokens, organized by concern. Every token name follows the naming pattern `--ak-{group}-{member}`.
| Group | Public tokens | Notes |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `color` | `accent`, `primary`, `primary-hover`, `text`, `text-muted`, `link`, `link-hover`, `link-visited`, `surface`, `surface-muted`, `surface-raised`, `border`, `border-strong`, `info`, `success`, `warning`, `danger` | The full semantic palette. `link` defaults to `primary`; override one and the other follows. |
| `font.family` | `body`, `heading`, `code` | High-level font stacks. |
| `font.size` | `xs` through `4xl` | The text size scale. |
| `font.weight` | `light`, `normal`, `bold` | `semi-bold` is intentionally omitted because PatternFly conflates it with `bold` under the default font stack. |
| `line-height` | `sm`, `md` | Compact vs default. |
| `space` | `xs` through `4xl` | The spacing scale. Use these instead of hardcoded `px` or `rem` values. |
| `radius` | `sm`, `pill` | `sm` for compact rounding; `pill` for fully-rounded badges and tags. |
| `border-width` | `sm`, `md`, `lg` | |
| `shadow` | `sm`, `md`, `lg`, `xl`, `inset` | Theme-aware — dark mode uses stronger shadow opacity. |
| `duration` | `normal` | The default transition duration. Adjust to make the whole interface feel snappier or calmer. |
| `easing` | `standard` | The default cubic-bezier curve. |
| `z-index` | `xs` through `2xl` | The stacking scale matches PatternFly's tiers (100..600). |
## Naming conventions
A few rules keep names predictable. If you're writing custom CSS or generating themes programmatically, these patterns hold across every token:
- **One semantic tier.** Token names express design decisions, not raw values. `--ak-color-primary` is the name; there's no public `--ak-color-brand-500` underneath it. If your brand needs a full custom palette, you remap the semantic tokens rather than reaching for a primitive palette.
- **A single size scale: `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl`.** Used consistently across `space`, `font.size`, `radius`, `border-width`, `shadow`, and `z-index`. There are no descriptive scales like `extraTight` or `loose`.
- **Spelled-out group names.** `z-index`, not `z`. `border-width`, not `bw`. This makes tokens discoverable in IDE autocomplete and brand custom CSS without having to memorize abbreviations.
- **Private variables use a different prefix.** Variables starting with `--_` or `--ak-c-` are component-local and undocumented. They may change between authentik releases. Avoid targeting them in custom CSS unless a release explicitly documents one as stable.
## Light and dark themes
The design language ships with two themes out of the box. Both are toggled by the `data-theme` attribute on `html`:
```css title="Switching themes"
html[data-theme="dark"] {
/* dark theme overrides apply here */
}
```
A third theme — `reduced` — is reserved for users who explicitly opt into reduced motion, in addition to the browser-level `prefers-reduced-motion` media query, which fires automatically. authentik respects both, so most operators do not need to do anything to support reduced motion users.
When you override semantic tokens in your custom CSS, follow the same pattern: provide light values under `:root`, then dark values under `html[data-theme="dark"]`. See [Custom CSS · Theme-based styling](./custom-css.mdx) for a worked example.
## Consuming tokens outside authentik
If your organization has its own design tooling, you can pull authentik's tokens in directly. The token system is published as the [`@goauthentik/theme`](https://www.npmjs.com/package/@goauthentik/theme) npm package, with three output formats:
| Path | Format | Use case |
| ---------------------------------------------- | ------------- | ---------------------------------------------------------------------------- |
| `@goauthentik/theme/index.css` | CSS | Include in a stylesheet to mirror authentik's tokens in another web surface. |
| `@goauthentik/theme/color.css` (and per group) | CSS | Select a single category — for example, just colors, or just shadows. |
| `@goauthentik/theme/tokens.dtcg.json` | DTCG 2025.10 | Import into Tokens Studio, Terrazzo, Figma, or any other DTCG-aware tool. |
| `@goauthentik/theme/tokens.dtcg.resolver.json` | DTCG resolver | Companion document that describes the `Dark` and `Reduced` theme modifiers. |
The DTCG JSON files are intended to be read or hand-edited; they are also formatted and validated as part of the package's build.
## Stability
The `--ak-*` semantic tokens are the public API. authentik treats them as a stable contract within a major version:
- Names won't be renamed without a deprecation cycle.
- Values may shift to follow design improvements, but the _role_ a token plays (primary, surface, text-muted, etc.) is stable.
- New tokens may be added.
- Tokens may be removed only after a deprecation notice in the release notes.
Anything _not_ in the `--ak-*` namespace — `--ak-c-*`, `--pf-*`, `--_*`, internal class names — is implementation detail. Custom CSS targeting those names may break between releases. Treat them as escape hatches for situations the semantic surface does not cover yet, and report any cases where you find yourself reaching for them so we can promote those to public tokens.
## Further reading
- [Custom CSS](./custom-css.mdx) — practical recipes and troubleshooting for brand custom CSS.
## References
- [Design Tokens Community Group](https://www.designtokens.org/)
- [Design Tokens Format Module 2025.10](https://www.designtokens.org/tr/2025.10/format/)
- [DTCG examples (terrazzoapp/dtcg-examples)](https://github.com/terrazzoapp/dtcg-examples)
- [Styleframe](https://styleframe.dev) — TypeScript CSS authoring system used by `@goauthentik/theme`.
- [Tokens Studio](https://tokens.studio/)
- [Terrazzo](https://terrazzo.io/)
- [PatternFly tokens](https://www.patternfly.org/tokens/about-tokens/)
- [PatternFly token usage](https://www.patternfly.org/tokens/develop-with-tokens/)
- [NL Design System token convention](https://nldesignsystem.nl/handboek/developer/design-token-conventie/)
- [GC Design System design tokens](https://design-system.canada.ca/en/styles/design-tokens/)
- [USWDS design tokens](https://designsystem.digital.gov/design-tokens/)
- [Open UI](https://open-ui.org/)
- [MDN cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer)
- [MDN CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*)
- [MDN CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts)

View File

@@ -0,0 +1,89 @@
---
title: Cascade layers
sidebar_label: Cascade layers
description: How authentik's web UI orders its CSS with @layer, and where each kind of rule belongs.
---
<!-- cspell:words unlayered -->
authentik's document CSS is ordered with [cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer). Layers make the ordering explicit, so a rule's precedence comes from the layer it is assigned to rather than from where it happens to be imported or how specific its selector is.
## The layer order
One declaration, in `web/src/styles/layers.css`, defines the order for the whole document. It is the first import in every entrypoint:
```css
@layer reset, vendor, components, theme, mode, brand;
```
Lowest precedence first:
| Layer | Holds |
| ------------ | ------------------------------------------------------------------------------------------------ |
| `reset` | Top-level normalization. Sparse — most of the reset is folded into PatternFly. |
| `vendor` | Vendored PatternFly and the bundled typefaces. Frozen; do not hand-edit. |
| `components` | Document-scope component rules, and the `:root` blocks bridging global tokens to component ones. |
| `theme` | The default design tokens. CSS custom property definitions only. |
| `mode` | Light/dark, high-contrast, and reduced-motion overrides of `theme`. |
| `brand` | Per-deployment branding overrides. |
`layers.css` is the only place the order is declared. Adding a layer anywhere else creates a second, independent ordering rather than extending this one.
## Assigning a rule to a layer
Layers are applied **only in the entrypoint files**, on the import itself:
```css
/* web/src/styles/interface.global.css */
@import "#styles/layers.css";
@import "#styles/global/vendor/patternfly.css" layer(vendor);
@import "@goauthentik/fonts/faces.css" layer(vendor);
@import "#styles/global/theme/variables.css" layer(theme);
@import "#styles/global/reset/globals.css" layer(reset);
@import "#styles/global/mode/mode.css" layer(mode);
@import "#styles/authentik/components/Placeholder/placeholder.css" layer(components);
```
The three entrypoints — `interface.global.css` (Admin and User), `flows.global.css` (Flow), and `static.global.css` (Django templates) — contain only `@import` statements. Individual stylesheets never wrap themselves in `@layer`, which keeps every precedence decision in one reviewable file per bundle.
Import order still matters _within_ a single layer. Across layers it does not: a later layer always wins, regardless of import order or selector specificity.
## Two cascade contexts
authentik has two independent cascades, and layers only govern the first.
**The document.** Driven by the three `.global.css` entrypoints, ordered by the layer stack above.
**Each shadow root.** Every component extending `AKElement` (`web/src/elements/Base.ts`) adopts, in order: `shadow/patternfly-base.css`, the component's own styles, `shadow/authentik-base.css`, and then brand custom CSS. These sheets are unlayered.
The bridge between the two is inheritance. CSS custom properties defined at the document level cross the shadow boundary, so a component reads `--ak-color-primary` without importing anything. What crosses is the _computed_ value — whichever declaration won at the document level.
This has a consequence worth internalizing:
> Layers order **selectors**, not properties.
Once a custom property crosses into a shadow root, the layer it was declared in no longer applies. Only its computed value carries over, and a declaration on `:host` inside the shadow root overrides what was inherited. Declare custom properties in consistent "dictionary" containers — `:root` at the document level, `:host` in shadow roots — so their specificity is predictable at the boundary.
## Brand customization
Brands customize appearance two ways, and they land in different places:
- **Custom properties from the database** are injected into the reserved `brand` layer, the last in the order, so they override theme and mode without needing `!important`.
- **A custom CSS file** is adopted per shadow root, after the component's own styles, so it can reach into `::part()` surfaces.
Because the custom CSS path is adopted last within its shadow root rather than being layered, existing `!important` declarations in brand CSS keep working. They are usually unnecessary.
## Adding CSS
- A new document-level component rule goes in `web/src/styles/authentik/components/<Name>/`, imported into the relevant entrypoint with `layer(components)`.
- A new design token goes in `web/src/styles/global/theme/`, which is already imported into `layer(theme)`.
- A light/dark or accessibility override goes in `web/src/styles/global/mode/`.
- Anything vendored from PatternFly goes through `web/src/styles/global/vendor/patternfly.css`.
`web/src/styles/README.md` is the in-repo companion to this page, with the full directory layout and a "what is where" index.
## References
- [MDN: cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer)
- [MDN: using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM)

View File

@@ -0,0 +1,123 @@
---
title: CSS architecture
sidebar_label: CSS architecture
description: How authentik's runtime CSS and design tokens fit together across the theme package, the document cascade, and Lit components.
---
<!-- cspell:words DTCG Styleframe -->
authentik's UI is a customized PatternFly 4 system wrapped in Lit web components. Treat PatternFly as a compatibility layer, not the public API to build on: the stable surface is the `--ak-*` token set.
## The pieces
- **`@goauthentik/theme`** owns the design tokens. Token modules are written in TypeScript, compiled by [Styleframe](https://styleframe.dev), and emitted as CSS custom properties plus a [DTCG](https://www.designtokens.org/) document.
- **`@goauthentik/fonts`** ships every typeface and its `@font-face` rules, separately from the tokens because they change on a different cadence. `faces.css` holds the RedHat brand text faces; `icons.css` holds the `pficon` and Font Awesome faces the PatternFly icon classes render glyphs from.
- **`web/src/styles/global/theme/token-bridge.css`** maps the semantic tokens onto the PatternFly variables that existing component CSS already reads.
- **`web/src/styles/`** assembles all of it into the three document bundles and the two per-shadow-root sheets. See [Cascade layers](./cascade-layers.md).
- **`web/src/elements/Base.ts`** adopts the shared sheets into every component's shadow root; `web/src/common/stylesheets.ts` centralizes `CSSStyleSheet` creation.
## Token tiers
| Tier | Prefix | Stability | Use |
| --------------- | --------------------- | ------------------ | ------------------------------------------ |
| Primitive | internal or generated | private | palette stops, raw scales, build input |
| Semantic | `--ak-*` | public | custom CSS, component styles, docs |
| Component-local | `--_*` or `--ak-c-*` | private by default | implementation detail inside one component |
Semantic names express a design decision, not a raw value:
```css
:root {
--ak-color-primary: oklch(0.518 0.1725 259.3 / 1); /* #0066cc */
--ak-color-surface: oklch(1 0 0 / 1); /* #ffffff */
--ak-space-md: 1rem;
--ak-radius-sm: 3px;
}
```
Do not promote component property names into the public surface:
```css
/* Avoid */
--ak-c-button-primary-background-color-hover-padding-left: 1rem;
```
## The token pipeline
Tokens are authored as typed modules under `packages/theme/src/tokens/`, one per category. Styleframe evaluates them into a variable tree, and the package build emits several shapes of the same data:
```text
packages/theme/src/tokens/*.ts typed token modules
-> Styleframe variable tree
-> dist/index.css every token, one file
-> dist/{color,typography,...}.css per-category slices
-> dist/dtcg/tokens.json DTCG interchange for design tooling
```
Colors are authored as hex and transformed to `oklch()` on the way out, each carrying the original hex in a trailing comment so editors still render a swatch.
DTCG is interchange data for tooling — Figma sync, validation, generated documentation. It is not a runtime format. The browser consumes CSS custom properties; keep runtime styling decoupled from the DTCG document.
## The PatternFly bridge
Most component CSS still reads `--pf-*`. `token-bridge.css` maps the semantic layer onto those names, so existing CSS keeps working while new CSS targets the shorter surface:
```css
:root,
:host {
--pf-global--primary-color--100: var(--ak-color-primary, var(--pf-global--primary-color--100));
--pf-global--spacer--md: var(--ak-space-md, var(--pf-global--spacer--md));
}
```
Each PatternFly variable falls back to its own prior value, so the bridge only overrides where a token exists. It is imported in two places — `global/theme/variables.css` for the document, and `shadow/patternfly-base.css` for shadow roots — so both cascades resolve the same way.
## Shadow DOM API
Custom properties are the configuration surface:
```css
ak-flow-executor {
--ak-color-primary: oklch(62% 0.2 260);
}
```
`::part()` is for exposed structure, and only where a brand can reasonably style that substructure without coupling to internal DOM:
```css
ak-flow-executor::part(locale-select) {
display: none;
}
```
Slots are for composition, not styling.
## Accessibility defaults
New component CSS should support `color-scheme: light dark`, `accent-color`, `prefers-color-scheme`, `prefers-reduced-motion`, `prefers-contrast`, `forced-colors`, and logical properties for right-to-left layouts.
Prefer semantic tokens that media queries adjust over separate per-variant theme files. Document-level light/dark, contrast, and motion overrides belong in `web/src/styles/global/mode/`.
## Guardrails
- Keep the public semantic set at roughly 30 to 60 names until real user needs justify more.
- Do not expose every CSS property as a public token.
- Do not document `--ak-c-*` as stable unless it is intentionally promoted.
- Do not generate tokens from PatternFly variable names.
- Keep part names short and structural: `control`, `label`, `icon`, `content`, `footer`.
- Keep direct custom CSS injection an advanced escape hatch, not the primary theming API.
## Still to do
- Migrate component markup off PatternFly incrementally, starting where styling is already mostly custom.
- Move component CSS next to the component it styles, rather than under `web/src/styles/authentik/components/`.
- Generate token reference documentation from the DTCG export instead of maintaining the table by hand.
- Offer live previews of token-level brand customization in the product.
## External references
- [Design Tokens Community Group](https://www.designtokens.org/)
- [Design Tokens Format Module](https://www.designtokens.org/tr/2025.10/format/)
- [Styleframe](https://styleframe.dev)
- [PatternFly tokens](https://www.patternfly.org/tokens/about-tokens/)
- [MDN: cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer)