Components/Theming

Theming

Four override layers, from global tokens down to a single component.

Since 0.4.0BETA

This component doc is in progress

This page is still being migrated. Demos and API details may change.

The model

No component hard-codes a color; every visual reads a CSS custom property, so re-theming is a stylesheet change. The four layers run widest first. Pick the narrowest one that solves the problem.

LayerWhere it livesScope
Global tokens:root in base.cssEverything
Theme selectors[data-theme='dark'], [data-tx-contrast='high']A whole mode
BUI tokens--tx-bui-*The AI suite
Component hooks--tx-<component>-*One component, or one subtree

The token inventory is in Design Foundations.

Global tokens

Redefine tokens after importing base.css. Components resolve them at paint time, so an override applies to the whole subtree below it.

EXAMPLE.CSS
@import '@talex-touch/tuffex/base.css';

:root {
  --tx-color-primary: #7c5cff;
  --tx-border-radius-base: 8px;
  --tx-transition-duration: 0.24s;
}

--tx-color-primary-soft and --tx-coloring-border-color are color-mix() expressions on --tx-color-primary, so they follow it. The primary ramp (--tx-color-primary-light-*, --tx-color-primary-dark-2) is fixed hex, and the default focus ring reads from it; override the ramp too when rebranding.

Dark mode

Dark is a selector, not a media query: set data-theme="dark" or the dark class on any ancestor.

EXAMPLE.HTML
<html data-theme="dark">
<!-- or -->
<html class="dark">
  • The two are equivalent, so Tuffex drops into a Tailwind app (.dark) or a data-theme app without an adapter.
  • Set either on a subtree to get a dark island on a light page.
  • Dark takes each of the three emphasis weights 40 lighter to offset the irradiation that makes light text on a dark ground look bolder: --tx-font-weight-medium 500 → 460, --tx-font-weight-semibold 600 → 560, --tx-font-weight-bold 700 → 660; body text stays at 400. Your own styles follow by reading var(--tx-font-weight-medium, 500). When overriding them, keep each dark value within (light − 100, light], or static fonts switch to another face; see Retinal Irradiation & Weight Tuning.

View Transitions

When toggling dark or light mode, Tuffex provides applyThemeTransition, a transition helper built on top of the native View Transitions API. The default preset is circle-blur, which expands outward from the pointer's click position using an SVG Gaussian-blurred mask for a soft, tactile glow.

Additional presets include circle (sharp circular clip), polygon (diagonal slash), polygon-gradient (gradient slash), wipe (horizontal wipe), fade (cross-fade), and custom GIF masks.

Loading demo...

Presets and options

Variant (variant)DurationDescription
circle-blur (default)450msExpanding circular glow with SVG Gaussian blur originating at click position
circle350msSharp circular clip-path expansion from click position
polygon600msDiagonal optical slash reveal
polygon-gradient800msDiagonal slash with linear gradient falloff
wipe400msHorizontal inset swipe
fade250msSmooth cross-fade transition
circle-blur-top-left600msBlurred circle originating from top-left corner
gif1500msCustom animated mask using maskUrl
  • Origin resolution: origin accepts a MouseEvent, { clientX, clientY }, or [x, y]. Defaults to viewport center if omitted.
  • Accessibility: Automatically falls back to immediate theme application when prefers-reduced-motion is active or if the browser lacks View Transition support.

High contrast

A fourth palette that raises text and border contrast in light and dark.

TriggerEffect
html[data-tx-contrast='high'] or html.contrastOn, explicitly
@media (prefers-contrast: more)On, following the OS
html[data-tx-contrast='normal']Opts out of the media query

With data-theme="dark" and either high-contrast trigger, the dark high-contrast palette applies; the two themes don't stack.

Materials

Under high contrast, components stop letting the page through:

PartUnder high contrast
Material panels: every TxBaseSurface mode but plain (Card, BaseAnchor panels, the command palette, the dialogs and the rest build on it; plain paints no material and gets no fill in high contrast either), GlassSurface, Toast, NavBar, TabBar, Picker, Slider, the RadioGroup indicator, the ProgressBar mask, StatCard, Drawer's transparent panel, MotionTransition's glass curtainAn opaque fill; no blur, no refraction, no rim light
Scrims: Modal, Drawer, BlowDialogKeep their dimming, lose the frost
Layers over content: LoadingOverlay, the Card and DataTable loading veils, ImageUploader's remove buttonOpaque, hiding what's under them
Decoration: FlowLight, the GlowText shine, GradualBlurHidden

Light tints inside a component (hover, selected and input fills) are unchanged. The rule lives in the tx-when-high-contrast mixin in style/mixins.scss, which answers to the same triggers as the palette.

AI suite tokens

--tx-bui-* came over with the Beautiful UI components (surface, inset, field, ink, and accent / green / orange / red / violet with matching tints) and has its own dark block under the same [data-theme='dark'] / .dark selectors. Override it when AI surfaces shouldn't inherit your brand hue:

EXAMPLE.CSS
:root {
  --tx-bui-accent: #7c5cff;
  --tx-bui-accent-tint: #f0ecff;
}

Component hooks

--tx-<component>-* restyles one component without touching the global palette. Names are predictable (--tx-collapse-header-bg, --tx-avatar-ring-color, --tx-progress-height), and each has a fallback. They inherit, so setting them on a wrapper themes that subtree only.

EXAMPLE.VUE
<template>
  <section class="settings-panel">
    <TxCollapse v-model="open">
      <TxCollapseItem title="Appearance" name="appearance">…</TxCollapseItem>
    </TxCollapse>
  </section>
</template>

<style scoped>
.settings-panel {
  --tx-collapse-radius: 6px;
  --tx-collapse-header-bg: transparent;
  --tx-collapse-border: rgba(148, 163, 184, 0.24);
}
</style>

Each component page lists the hooks it reads. Use them instead of descendant selectors into component internals: custom properties are the supported surface, class names are not.

Caveats

  • When a component exposes a fill hook, set the token, not background-color: some fills are background-image gradients, and background-color paints behind them.
  • Import base.css exactly once. With two copies, the one that loads last wins, which is rarely the one you edited.

Technologies

  • Tokens and theme selectors: packages/tuffex/packages/components/style/variables.scss.
  • AI suite tokens: packages/tuffex/packages/components/style/bui-tokens.scss.
  • Runtime entry: @talex-touch/tuffex/base.css.