GUIDE
Theming
Every dj-* component reads its colors, spacing, type, and sizes from --dj-* custom properties. One stylesheet gives you light, dark, and high-contrast themes. A few overrides make it your brand.
data-dj-theme on the panel; the brand button applies the overrides shown under “Make it your brand.”Load the theme
Every component carries fallback values and renders without a theme, so this step is optional. @dojo-ng/theme supplies the full token set. Link it once per page, or add @dojo-ng/theme/theme.css to your bundler's global styles. Custom properties inherit through shadow roots, so one page-level stylesheet reaches every component.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dojo-ng/theme/theme.css">
Switch themes
With no attribute set, the theme follows the operating system's light or dark setting. To choose one, set data-dj-theme on <html> to light, dark, or high-contrast. Components restyle immediately; none of them needs to be told.
// "light", "dark", or "high-contrast". Remove the attribute to follow the OS again.
document.documentElement.dataset.djTheme = "dark";
high-contrast is black on white with 2px input borders and a 3px focus ring, for users who need more than the dark theme's contrast. Windows High Contrast Mode is handled separately, below.
How the tokens are layered
Tokens come in three layers, and each layer refers to the one beneath it. Override at the lowest layer that does the job.
| Layer | Examples | Override it to |
|---|---|---|
| Primitives | --dj-color-primary-600, --dj-color-neutral-0…900, --dj-spacing-medium, --dj-font-size-small | Change the palette or scale everywhere. |
| Semantic roles | --dj-color-text, --dj-color-background, --dj-color-border, --dj-focus-ring-color | Change what a role uses without touching the palette. |
| Component tokens | --dj-button-font-size-medium, --dj-input-height-medium, --dj-loading-linear-height | Make one narrow adjustment. |
The dark theme works by remapping primitives. Its neutral scale runs the other way, so --dj-color-neutral-0 becomes the near-black background, and the semantic roles that point at it follow. theme.css lists every token in every theme.
Make it your brand
Load a short stylesheet after theme.css that sets your primary scale. Mirror the selectors theme.css uses, or one of the themes will fall back to the default blue. Your light values belong on :root when no theme is chosen and on [data-dj-theme="light"]. Your dark values belong on [data-dj-theme="dark"] and in the OS dark-mode query. The high-contrast theme is left alone on purpose, since its blues are chosen for contrast.
/* brand.css: load after theme.css */
:root:not([data-dj-theme]),
[data-dj-theme="light"] {
--dj-color-primary-500: #8b5cf6;
--dj-color-primary-600: #7c3aed;
--dj-color-primary-700: #6d28d9;
}
[data-dj-theme="dark"] {
--dj-color-primary-500: #a78bfa;
--dj-color-primary-600: #8b5cf6;
--dj-color-primary-700: #c4b5fd;
}
@media (prefers-color-scheme: dark) {
:root:not([data-dj-theme]) {
--dj-color-primary-500: #a78bfa;
--dj-color-primary-600: #8b5cf6;
--dj-color-primary-700: #c4b5fd;
}
}
This shows three steps of the scale. A full brand sets --dj-color-primary-50 through 900, and the dark theme redefines 100, 500, 600, and 700. For a deeper change, copy theme.css, edit it, and load your copy instead. Components need no changes either way.
Make a targeted tweak
Custom properties inherit, so a token set on any element applies only to that element's subtree. Use component tokens on a container or a single element when you want one area to differ:
/* Larger buttons in the toolbar only */
.toolbar {
--dj-button-font-size-medium: 1.125rem;
--dj-input-height-medium: 3rem;
}
Theme part of a page
data-dj-theme works on any element, not only <html>, so a dark sidebar or a high-contrast panel takes one attribute. The <dj-theme> element from @dojo-ng/theme does the same as a component, with theme set to light, dark, or auto. auto removes the attribute, so the subtree takes whatever theme surrounds it.
<dj-theme theme="dark">
<dj-button>Always dark</dj-button>
</dj-theme>
<!-- or, with no element to register -->
<aside data-dj-theme="high-contrast">…</aside>
Reach inside with ::part()
Shadow DOM keeps page CSS out of a component's internals. When a token does not cover what you need, components expose their significant internal elements as parts, which page CSS can style directly. Each component's README lists its parts; dj-button has base, label, and icon. Treat parts as the escape hatch and tokens as the main interface.
dj-button::part(label) {
letter-spacing: 0.02em;
text-transform: uppercase;
}
Style validation states
A form control's native <input> lives inside its shadow root, so :invalid cannot reach it from page CSS. Controls such as dj-text-input draw their own invalid state, and every form control also mirrors their validity onto the host element as attributes you can style against: data-dj-required, data-dj-valid, data-dj-invalid, data-dj-user-valid, and data-dj-user-invalid. The user- pair only appears after the user edits and leaves the field, or submits the form, so an untouched field is not flagged.
/* Show a hint only after the user has had a chance to fill the field */
.field:has(dj-text-input[data-dj-user-invalid]) .hint {
display: block;
}
In browsers that support custom states, the same five names also work as :state(user-invalid) and so on.
Windows High Contrast Mode
When the operating system forces its own colors, theme.css maps the focus ring to the system highlight color and makes overlay backdrops opaque. Each component adds the rest inside its own styles, such as borders where it used shadows and system colors for checked and selected states. There is nothing to configure.