Skip to main content

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:

  1. The light and dark SCSS variable files define Artemis semantic roles and compile them to CSS custom properties.
  2. src/main/webapp/tailwind.css exposes selected roles as semantic Tailwind utilities.
  3. The same file maps Artemis foundations and color roles to the public --tumaet-ui-* contract. TUM UI components consume only that package-owned contract.
  4. 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:

  1. TUM UI component input or variant. Use severity, variant, size, and other supported contracts instead of overriding component internals.
  2. Semantic Tailwind utility. Use one when Artemis owns the mapping, such as text-state-danger. PrimeUI surface scales are not Artemis design tokens.
  3. Artemis semantic CSS property. Use an existing role in component SCSS or an irreducible dynamic style when a utility cannot express the requirement.
  4. 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:

IntentArtemis 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:

  1. Name it after purpose rather than hue or a single screen.
  2. Add values to src/main/webapp/content/scss/themes/_default-variables.scss and _dark-variables.scss.
  3. Check text and non-text contrast on every surface where the role is used.
  4. Add a semantic Tailwind mapping only when application markup needs a utility.
  5. Add a --tumaet-ui-* mapping only when package components need that supported customization axis; update the package reference theme and validation with it.
  6. 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.

Search documentation