Default styles
UniAria has no look, and everything in Styling works without
this page. @uniaria/styles adds one: a neutral look for all 17 components,
accessible in every scheme, which you can keep, retheme
(Theming) or replace part by part. The demos on these pages
use it.
It comes three ways, all from one source written as Tasty style objects:
| Output | For | Needs |
|---|---|---|
| Prebuilt CSS | Any project: React, Vue or custom elements | Nothing: plain stylesheets |
| Customized CSS | Your tokens, palette and overrides, built in Node | @tenphi/tasty, and @tenphi/glaze for a palette of your own |
| Tasty style objects | A project that styles its components with Tasty already | @tenphi/tasty |
pnpm add @uniaria/stylesTasty and Glaze are optional peer dependencies: add them only for the outputs that need them.
@import '@uniaria/styles/css';That is everything: the tokens and all 17 components. The package has a file for each part of it too:
| Import | Holds |
|---|---|
@uniaria/styles/css |
The tokens and every component. |
@uniaria/styles/css/layered |
The same, inside @layer uniaria. |
@uniaria/styles/css/tokens.css |
The sizes, the durations, and the palette’s colors in every scheme. |
@uniaria/styles/css/components.css |
Every component and the sizes, without colors: for colors of your own. |
@uniaria/styles/css/<name>.css |
One component, such as checkbox.css. It needs tokens.css. |
@uniaria/styles/css/positioning.css |
Opt-in: each overlay’s default placement in CSS positioning mode. |
So a page with a few components takes the tokens and those:
@import '@uniaria/styles/css/tokens.css';
@import '@uniaria/styles/css/checkbox.css';
@import '@uniaria/styles/css/select.css';In CSS positioning mode, UniAria
leaves placing overlays to your CSS. positioning.css places each one on its
component’s default side with CSS anchor positioning, where the browser has
it, and the component’s styles keep the gap from the trigger as a margin.
Without it, place the overlays in your own CSS.
The styles select the markers and state attributes UniAria writes
(Parts), and add no class or attribute of their own,
with one exception: data-variant on a Button or a ToggleButton, set to
primary, quiet or danger; without it, the button is secondary. A few
parts expect an element of yours, such as Switch’s Thumb; each component
page’s “Default styles” note says what it styles and expects.
A Button or ToggleButton with a label part, a Popover’s or Dialog’s trigger
with a triggerLabel, and an option with an optionLabel are laid out as
items:
- Around the label. The element before the label, such as an icon, sits at its start, and the one after it, such as a shortcut hint or a count, at its end, half a gap away. One element each: wrap several in one.
- One line. The label and its description below take one line each,
aligned to the start, and end in an ellipsis, as do options without a
label part. Pair cut-off text with an
overflow tooltip. For more lines, set
-webkit-line-clampon the part yourself. - Frames. In a
frame, the button spans it and theextrassit over its end: the label ends before them, and the button’s fill, border and focus ring stay under them in every state. Only what acts in the extras takes the pointer (buttons, links, UniAria components and tooltip triggers), so a count or a gap between extras presses the button. A framed button needs a label part, which keeps its content clear of the extras. A Button in the extras is 24 pixels high. - Colors. The frame takes its button’s text color,
data-variantincluded, so extras drawn incurrentColormatch it. A quiet button takes the text color around it and mixes its hover and press fills from it, so it reads on any fill, a primary trigger’s or a selected option’s. - Pickers. Select’s chevron comes after its trigger’s extras, and an option’s extras come before its check.
Every rule’s specificity is flat, whatever the state: a root’s is (0,1,0) and
a part’s (0,2,0), since Tasty wraps states in :where().
-
The same selector, loaded after the styles, wins in every state:
[data-uni='checkbox'] [data-element='Indicator'] { border-radius: 2px; } -
One more attribute or class wins in any order:
[data-uni='checkbox'] [data-element='Indicator'][data-selected]. -
A class of yours on a root wins when it loads after the styles.
-
A reset never wins: an element rule such as
button { … }is (0,0,1).
The styles have no layer, unless you put them in one of yours:
@import '@uniaria/styles/css' layer(components);With Tailwind 4, components sits above Preflight and below the utilities.
Where an import can’t name a layer, as in a <link> or a CSS file imported
from JavaScript, take the layered file, and declare where its layer goes once,
before any import:
@layer base, uniaria, utilities;
@import '@uniaria/styles/css/layered';UniAria’s own global rules sit in an anonymous layer, which the unlayered styles outrank; the styles never set what those rules do (Styles UniAria sets).
buildCss() builds the same stylesheet in Node, with your changes. Run it in
your build:
import { writeFileSync } from 'node:fs';
import { buildCss } from '@uniaria/styles/build';
import { createPalette } from '@uniaria/styles/palette';
writeFileSync(
'uniaria.css',
buildCss({
palette: createPalette({ accent: { hue: 150 } }),
tokens: { '$uni-radius': '4px' },
components: ['button', 'checkbox', 'select'],
overrides: { checkbox: { Indicator: { radius: '0.5r' } } },
}),
);| Option | Default | What it does |
|---|---|---|
palette |
The package’s | The colors: a palette from createPalette(), or false to leave them out. |
tokens |
None | Tokens over the sizes and the palette, as CSS: one value, or one per scheme (Theming). |
components |
All 17 | The components to style; [] writes the tokens only. |
overrides |
None | Tasty styles merged into a component’s, by its name (checkbox), or into a part outside its root, by its marker ('popover-trigger'). |
extraThemes |
['warning', 'note'] |
The status themes to write, which no component uses: they are there for yours. |
schemes |
Dark and high contrast | { dark: false } or { highContrast: false } leaves those schemes out. |
layer |
None | A cascade layer for the whole file. |
Overrides take Tasty’s syntax and its merge rules: a state map without a ''
key extends the default one, a map with '' replaces it, and null removes a
style or a part’s styles. Their units read the size tokens: 1x is the gap,
1r the radius, 1bw the border width and 1ow the outline width.
buildCss() is synchronous and runs in Node only. Tasty keeps one
configuration per process, so each call sets it, builds, and resets it before
it returns.
In a project that uses Tasty, take the source itself. Each component’s object
is an entry of its own, @uniaria/styles/<name>, and the parts outside a
root have theirs (dialogTriggerStyles, popoverTriggerStyles):
import { configure, tasty } from '@tenphi/tasty';
import { checkbox } from '@uniaria/core/checkbox';
import { useBehavior } from '@uniaria/react';
import { checkboxStyles } from '@uniaria/styles/checkbox';
import { uniaria } from '@uniaria/styles/tasty';
// Once, before anything renders.
configure({ plugins: [uniaria()] });
const CheckboxRoot = tasty({ styles: checkboxStyles });
export function Terms() {
const terms = useBehavior(checkbox, { labelElementType: 'span' });
return (
<CheckboxRoot {...terms.parts.root}>
<label {...terms.parts.wrapper}>
<input {...terms.parts.input} name="terms" />
<span {...terms.parts.indicator} />
<span {...terms.parts.label}>Accept the terms</span>
</label>
</CheckboxRoot>
);
}The objects style the parts by the data-element markers UniAria writes, and
their states are the attributes it writes, so a tasty() element takes the
part props as they are. Extend an object as any Tasty styles:
tasty(CheckboxRoot, { styles: { Indicator: { radius: '0.5r' } } }).
uniaria() brings what the objects need: the tokens of every scheme, as
global styles, and the state aliases they use (@uni-reduced-motion and the
like). It sets no units, so the objects take your project’s spacing and radii.
Its options, a palette and tokens, are on Theming.
The package has a config for Tasty’s ESLint plugin, with its tokens and
aliases. Extend it from your tasty.config.ts:
export default {
extends: '@uniaria/styles',
tokens: ['#brand'],
};- The page’s layout. Components get their sizes and inner spacing, never margins or placement.
- Behavior. The styles change nothing a component does or writes, and leave alone what UniAria styles itself.
- Icons and fonts. Check marks, chevrons and the stepper signs are drawn in CSS; text takes the page’s font, at the sizes of the tokens.
- Custom element hosts. The styles target the parts inside
<uni-checkbox>, never the host, which keeps its default display.