{"revision":"c10276e953456835cdbb1f3a60d58ae1980be353","document":{"id":2743,"path":"application-development/theme-js-configuration-reference.md","title":"theme.js configuration reference","bundle":null,"doc_type":null,"release":"australia","body":"\n# theme.js configuration reference\n\nA Lux application declares its brand in one file at the project root, and the framework generates every color, radius, and font token from it. Restyling a Lux application goes through that file rather than through component CSS.\n\n**Important:** Two files carry the name `theme.js`, and this page is about your application's own root `theme.js`, which holds four keys of brand input. The framework's internal token-definitions module, `libraries/theme-generation/src/algorithm/theme.js`, is documented in . If you are looking for `ROOT_TOKENS`, look there.\n\n## Scaffolded theme file\n\nA scaffolded application ships with the following file.\n\n```\n// theme.js\nexport const AppTheme = {\n  primary_color: '#032D42',\n  neutral_colors: {use_hex_value: true, hex_value: '#F8F7F4'},\n  shape: {\n    selector_radius: '0.75rem',\n    field_radius: '0.5rem',\n    boxes_radius: '1rem',\n    border_width: '1px'\n  }\n};\n```\n\nThat is the whole file. The generated palette propagates to every component in the application.\n\n## theme.js Fields\n\n|Field|Type|What it controls|\n|-----|----|----------------|\n|`primary_color`|Hex string|Brand color. Drives the `--primary-*` scale, buttons, links, and focus rings.|\n|`accent_color`|Hex string|Secondary calls to action, through the `--accent-*` scale.|\n|`neutral_colors`|`{use_hex_value: true, hex_value: '#…'}`|Base for the `--neutral-*` scale, which most surface, text, and border tokens resolve through.|\n|`shape`|Object|Four radius and width keys: `selector_radius` for check boxes, radios, and toggles, `field_radius` for inputs and buttons, `boxes_radius` for cards and panels, and `border_width`.|\n|`fonts`|Array|Font families to register as `@font-face`.|\n|`light_logo` / `dark_logo`|Attachment reference|Per-scheme logo shown in the nav header.|\n|`light_favicon_url` / `dark_favicon_url`|URL|Per-scheme favicon.|\n|`token_overrides`|Object|Override for individual token values.|\n\nOnly `primary_color` is required. Every other field falls back to a Lux default.\n\nThe four `shape` keys map onto the tokens documented in .\n\n## Applying the theme\n\nExport the theme from your application's lifecycle module. Nothing else is required.\n\n```\n// application.js\nimport {AppTheme} from './theme.js';\n\nexport default {\n  appTheme: AppTheme,\n  applicationLayout: 'aiux-nav-layout'\n};\n```\n\nWhen your application has no lifecycle module, set the theme as a static on your layout class instead.\n\n```\n// pages/layout.js\nimport {AIUXAppLayoutElement} from '@servicenow/aiux-components-core';\nimport {AppTheme} from '../theme.js';\n\nexport default class AppLayout extends AIUXAppLayoutElement {\n  static appTheme = AppTheme;\n}\n```\n\n## Precedence\n\nThe following table lists the four theme sources, highest precedence first.\n\n|Source|Notes|\n|------|-----|\n|A theme provisioned on the instance|Wins over everything in code, so an admin can rebrand the same application without a code change|\n|`application.js`'s `appTheme` export|Wins over the layout static|\n|`static appTheme` on an `AIUXAppLayoutElement` subclass|Fallback when there is no lifecycle module|\n|The framework default theme|Applies when none of the other sources is present|\n\n## Generated color scales\n\n`primary_color`, `accent_color`, and `neutral_colors` are base colors rather than tokens. Each is expanded into an 11-stop scale, `50` through `950`, generated twice, once for light mode and once for dark, so contrast holds in both. The semantic tokens are defined in terms of those stops.\n\nThat expansion is why one hex value is enough to rebrand an application, and why component styles should use a semantic token rather than a raw stop. See .\n\n## token\\_overrides\n\n`token_overrides` sets any design token without a change to component code. Every built-in token is defined with an override hook already in place, so a key matching a known token replaces that token's value everywhere it is consumed. A key matching nothing is emitted as a new custom property you can `var()` yourself.\n\nKeys at the top level apply everywhere. Nest them under `light`, `dark`, `comfy`, or `cozy` to scope an override to one color scheme or density mode.\n\n```\nexport const AppTheme = {\n  primary_color: '#032D42',\n  token_overrides: {\n    '--radius-lg': '1.5rem',        // known token: every card gets rounder\n    '--font-sans': '\"Demo Grotesk\"',\n    '--brand-glow': '0 0 12px #63DF4E', // unknown key: new property, use via var()\n\n    light: {'--color-primary': '#032D42'},\n    dark: {'--color-primary': '#63DF4E'},\n    comfy: {'--space-unit': '0.4rem'},\n    cozy: {'--space-unit': '0.1rem'}\n  }\n};\n```\n\n## Key to selector\n\n|Source key|Selector emitted|\n|----------|----------------|\n|Root-level keys|`:root`|\n|`light`|`[data-theme=\"light\"]`|\n|`dark`|`[data-theme=\"dark\"]`|\n|`comfy`|`[data-density=\"comfy\"]`|\n|`cozy`|`[data-density=\"cozy\"]`|\n\nA theme with no `token_overrides`, or with an empty object, emits no override block.\n\n## Key resolution\n\n`generateTokenOverrideBlocks()` checks each key against `ROOT_TOKENS`, `DATAVIS_ROOT_TOKENS`, `LIGHT_THEME_TOKENS`, `DARK_THEME_TOKENS`, `DENSITY_COMFY`, and `DENSITY_COZY`. A match is renamed to `--theme-level-<name>`, which feeds the `var(--theme-level-<name>, <default>)` indirection already baked into that token. A non-match passes through unprefixed.\n\n## Application CSS precedence\n\nTokens declared in your application's `tailwind.app.css` sit above the theme in the cascade, so a value pinned there isn't affected by whatever a theme sets. Use `token_overrides` for what a rebrand should be able to change, and `tailwind.app.css` for what it should not.\n\n## Storage and request flow\n\nThe field is stored as a JSON blob on the `sys_aix_theme` record. It is an empty string, rather than `null` or `undefined`, when a theme declares no overrides.\n\nA theme's `theme.js`, or an admin-authored theme record, declares `token_overrides`. `collectThemeRecords()` stringifies it into the `sys_aix_theme.token_overrides` column at build time, and `theme-reader.js` parses it back on every request. `getAppThemeCSS()` calls `generateThemeCSS()`, which includes the override block in the server-rendered `<style id=\"theme-token-overrides\">` tag. On a client-side theme change, `applyTheme()` regenerates and swaps the same block without a full reload.\n\nThe mechanism is implemented, with the font route noted in the preceding note.\n\n## Switching light and dark at runtime\n\nTheme mode lives on the `data-theme` attribute. The framework exposes a service for flipping it, and the user's choice persists across reloads.\n\n```\nimport {\n  setThemeMode,\n  getThemeMode,\n  themeContext\n} from '@servicenow/aiux-components-core';\n\n_toggleTheme() {\n  setThemeMode(getThemeMode() === 'dark' ? 'light' : 'dark');\n}\n```\n\nMost applications don't need to write that code, because the nav layout ships a theme toggle that you turn on with `features.themeToggle`.\n\n## Reacting to the theme mode\n\nConsume `themeContext` and reflect the value onto your own host as `theme-variant`.\n\n```\nstatic contexts = [themeContext];\n\nwillUpdate() {\n  this.setAttribute('theme-variant', themeContext.get()?.mode ?? 'light');\n}\n\nstatic styles = css`\n  :host([theme-variant='dark']) .panel {\n    box-shadow: none;\n  }\n`;\n```\n\nUse `theme-variant` on the host rather than `data-theme`. The generated theme CSS declares `[data-theme]` rules at the document level, and those outrank a `:host` rule for the same token. Reflecting your own attribute keeps the override inside your shadow root, where it applies.\n\n"}}