Theming
Four override layers, from global tokens down to a single component.
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.
| Layer | Where it lives | Scope |
|---|---|---|
| Global tokens | :root in base.css | Everything |
| 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.
@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.
<html data-theme="dark">
<!-- or -->
<html class="dark">
- The two are equivalent, so Tuffex drops into a Tailwind app (
.dark) or adata-themeapp 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-medium500 → 460,--tx-font-weight-semibold600 → 560,--tx-font-weight-bold700 → 660; body text stays at 400. Your own styles follow by readingvar(--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.
Presets and options
Variant (variant) | Duration | Description |
|---|---|---|
circle-blur (default) | 450ms | Expanding circular glow with SVG Gaussian blur originating at click position |
circle | 350ms | Sharp circular clip-path expansion from click position |
polygon | 600ms | Diagonal optical slash reveal |
polygon-gradient | 800ms | Diagonal slash with linear gradient falloff |
wipe | 400ms | Horizontal inset swipe |
fade | 250ms | Smooth cross-fade transition |
circle-blur-top-left | 600ms | Blurred circle originating from top-left corner |
gif | 1500ms | Custom animated mask using maskUrl |
- Origin resolution:
originaccepts aMouseEvent,{ clientX, clientY }, or[x, y]. Defaults to viewport center if omitted. - Accessibility: Automatically falls back to immediate theme application when
prefers-reduced-motionis active or if the browser lacks View Transition support.
High contrast
A fourth palette that raises text and border contrast in light and dark.
| Trigger | Effect |
|---|---|
html[data-tx-contrast='high'] or html.contrast | On, 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:
| Part | Under 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 curtain | An opaque fill; no blur, no refraction, no rim light |
| Scrims: Modal, Drawer, BlowDialog | Keep their dimming, lose the frost |
| Layers over content: LoadingOverlay, the Card and DataTable loading veils, ImageUploader's remove button | Opaque, hiding what's under them |
| Decoration: FlowLight, the GlowText shine, GradualBlur | Hidden |
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:
: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.
<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 arebackground-imagegradients, andbackground-colorpaints behind them. - Import
base.cssexactly 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.