Client Theming
Artemis supports light and dark themes. The active scheme is exposed as
data-theme="light" or data-theme="dark" on the document element so application styles, TUM UI,
and embedded content share one framework-neutral signal.
For design intent and accessibility requirements, follow the AET UI/UX color guidelines. This page documents how Artemis implements those decisions.
Theme architecture
The theming boundary has four layers:
- The light and dark SCSS variable files define Artemis semantic roles and compile them to CSS custom properties.
src/main/webapp/tailwind.cssexposes selected roles as semantic Tailwind utilities.- The same file maps Artemis foundations and color roles to the public
--tumaet-ui-*contract. TUM UI components consume only that package-owned contract. - Bootstrap and PrimeNG receive compatible values while they remain in the application. They are migration consumers, not the source of Artemis design decisions.
This keeps one semantic source of truth while allowing legacy frameworks to leave independently.
Do not reference Artemis properties from package source, and do not reference --tumaet-ui-*
directly from application components. Change the host mapping when a package-wide theme decision
changes.
Choosing styles
Use the first suitable option:
- TUM UI component input or variant. Use
severity,variant,size, and other supported contracts instead of overriding component internals. - Semantic Tailwind utility. Use one when Artemis owns the mapping, such as
text-state-danger. PrimeUI surface scales are not Artemis design tokens. - Artemis semantic CSS property. Use an existing role in component SCSS or an irreducible dynamic style when a utility cannot express the requirement.
- New semantic role. Add one only when the concept recurs and no existing role has the correct meaning. Define both light and dark values and expose it to TUM UI only when it is a supported package customization axis.
Common Artemis roles include:
| Intent | Artemis role |
|---|---|
| primary action and link | --primary, --primary-dark |
| default and muted text | --body-color, --text-body-secondary |
| content and overlay surfaces | --module-bg, --popover-bg, --hover-slightly-darker-body-bg |
| border | --border-color |
| state | --danger, --success, --warning, --info |
| state message | --artemis-alert-{danger,success,warning,info}-{color,background,border} |
Use the state-* Tailwind utilities for state-colored plain markup:
<small class="text-state-danger" id="title-error"> {{ 'entity.validation.required' | artemisTranslate }} </small>
Use TUM UI for a component-shaped status:
<tum-ui-message severity="error"> {{ 'entity.validation.required' | artemisTranslate }} </tum-ui-message>
Color must never be the only signal. Pair a state color with text, an icon, or another programmatic cue as required by WCAG 1.4.1.
Adding or changing a theme role
Before adding a property, search the existing semantic roles and the TUM UI theme contract. If a new role is necessary:
- Name it after purpose rather than hue or a single screen.
- Add values to
src/main/webapp/content/scss/themes/_default-variables.scssand_dark-variables.scss. - Check text and non-text contrast on every surface where the role is used.
- Add a semantic Tailwind mapping only when application markup needs a utility.
- Add a
--tumaet-ui-*mapping only when package components need that supported customization axis; update the package reference theme and validation with it. - Verify the real component in both themes, compact layouts, forced-colors mode, and its relevant interaction states.
Avoid theme-specific component selectors in theme-default.scss or theme-dark.scss. They are a
last resort for legacy global integrations. A new application or TUM UI component should consume
semantic properties instead.
Reading and changing the active theme
CSS should normally react through data-theme and semantic properties without TypeScript. When
behavior genuinely depends on the active theme, consume the ThemeService.currentTheme signal:
private readonly themeService = inject(ThemeService);
protected readonly isDark = computed(() => this.themeService.currentTheme() === Theme.DARK);
Code that reads resolved CSS values after a stylesheet swap, such as a canvas chart, must also
depend on ThemeService.appliedThemeRevision. This signal changes only after the selected theme
stylesheet is active.
Call applyThemePreference() only in response to an explicit user choice. Pass undefined to
restore system-preference mode:
this.themeService.applyThemePreference(Theme.DARK);
this.themeService.applyThemePreference(undefined);
Do not subscribe manually to theme changes or mirror the current theme into a mutable field.
Migration coexistence
Bootstrap is still global, and PrimeNG remains in screens whose TUM UI capability is incomplete. Their cascade and source-scanning constraints are documented in Client Development: Styling.