@teacss/preset-standard
v0.8.1
Published
The standard utility vocabulary for TeaCSS — spacing, sizing, color, layout, typography, and more.
Readme
@teacss/preset-standard
The standard TeaCSS utility vocabulary, theme, conditions, and preflight.
Standard intentionally owns no shortcuts. Define product compositions with
@shortcut or enable @teacss/preset-official separately.
Usage
bun add @teacss/preset-standard@presets "standard";
@source "./src/**/*.{ts,tsx}";
@teacss;Programmatic consumers use presetStandard(). Public value tables are
available from @teacss/preset-standard/constants:
import {
boxDecorationValues,
globalKeywords,
multiplierKeywords,
} from "@teacss/preset-standard/constants";Each *Values export contains common property values. CSS-wide keywords are
separate. Use multiplierKeywords for the numbered scale.
Application shortcuts
@shortcut stack-row "d:flex flex-direction:row ali:center";
@shortcut text-truncate "overflow:hidden text-overflow:ellipsis white-space:nowrap";Declarations may live in the CSS entry, imported CSS, or CSS matched by
@source. Conditions remain valid on use, and the last declaration of a name
wins. Programmatic shortcuts use Core's generic UserConfig types.
Paired sizing and logical dividers
For text-flow sizing, size:4x, min-size:4x, and max-size:4x set both
inline and block sizes. For fixed geometry, use dimensions:4x,
min-dimensions:4x, and max-dimensions:4x for width and height. These are
ordinary Standard declarations. Values must be accepted by both underlying
single-axis rules. Variables, arbitrary values, and suffix conditions work as
they do on those rules. size:screen emits 100vi and 100vb;
dimensions:screen emits 100vw and 100vh.
divide-inline-width: and divide-block-width: draw on logical inline-end
and block-end edges. divide-x-width: and divide-y-width: keep physical
right and bottom edges. divide-color: and divide-style: supply common
settings. divide-inline-color/style: and divide-block-color/style: override
only the named logical axis. Color and style alone do not draw a divider; each
axis needs its own width. To change axes at a breakpoint, set the old width to
0 and the new width explicitly.
Preflight and themes
Preflight emits low-specificity reset rules and theme variables. The default,
preflight: "on-demand", limits variables to referenced keys (including the
reset's sans and monospace families). Set preflight: true to emit every theme
and @custom variable for CSS or inline styles that read them directly, or
preflight: false to disable both.
Selecting @presets "standard" also normalizes headings, links, lists, media,
tables, horizontal rules, code typography, and form-control typography. Headings
inherit size and weight; links inherit color and decoration; lists lose their
markers; images and videos fit their container without losing aspect ratio.
Use utilities or Official's prose family to author their visible presentation.
Small text, sub/sup, titled abbreviations, and progress have consistent baselines.
Dialogs and popovers retain native auto-margin centering after the universal
spacing reset; opening and closing remain browser-controlled.
File-selector buttons inherit input typography and color while keeping native
appearance. Fieldsets use min-inline-size: 0 so their boxes can shrink inside
narrow containers; oversized descendants still need responsive sizing.
Grouped options in select[multiple] and select[size] retain a 20px
inline-start indent, following text direction and allowing utility overrides.
The reset enforces native hiding with two global rules:
:where([hidden]:not([hidden="until-found" i])) { display: none !important; }
:where([hidden="until-found" i]) { display: revert !important; }Ordinary display utilities, component CSS and inline styles cannot reveal an
element with hidden; remove the attribute to show it. Explicit !important
declarations still follow the CSS cascade. until-found restores the tag's
native display, with discovery depending on the tag and browser. Prefer div
or section containers for discoverable content:
<section hidden="until-found">Searchable content</section>These rules run in both full and on-demand preflight. With preflight disabled, the application supplies its own hiding rules.
Native control appearance, focus outlines, disabled state, and disclosure
behavior remain intact. Marker-free semantic lists should use role="list"
for Safari/VoiceOver. The reset adds no brand palette or global motion policy.
Element-local reset-* shortcuts belong to the optional Official preset;
Standard does not register those classes.
Global role palettes and color scheme
Standard has seven roles: primary → ink, neutral → gray, focused →
neutral, success → jade, warning → amber, failure → red, and general → blue.
Use numbered roles directly, such as bg-color:primary-800 text-color:primary-000;
steps follow Radix's layout: 100–400 backgrounds, 500–700 borders,
800/850 solids, 900/950 text. 000 is the text on 800/850, Radix's
contrast color: white for most families, dark for sky mint lime yellow amber,
and flipping with ink. A family without 000 resolves <family>-000 to white.
Official adds structure without background/hover/foreground color aliases.
Each use writes one step for both modes; when a pair misses contrast, the palette
changes, not the class. Neutral builds structure with every step: page 150,
panels 100 without borders, hover 300, borders 500–700. Signal roles
appear only in a few forms. Primary: the brand face (800 + 000, one button
per view), the value (900 for links, icons, indicators and checked fills, with
neutral-100 marks), and selection (200, 300 hovered, text 950). States: the value
(900 for text, icons and validation borders), the soft badge (200 + 950),
and the strong face (900 + neutral-100, 950 hovered). 900 + neutral-100
reaches 4.5:1 for every family; 800 + 000 reaches Radix's 3:1. Alpha serves
only disabled text (neutral-900/60) and scrims. Charts write dictionary names,
such as fill-color:office-900; the entry dictionary binds office to indigo.
ink is a complete 12-step physical palette in both modes: gray surfaces,
near-black light-mode and near-white dark-mode 800/850 fills, and 900/950 text.
Consume it through primary; it adds no role. gray and sage remain available.
Absent role attributes use ink/gray defaults without candidate lists. Explicit
html selections, including ink, still require the corresponding role list.
lime uses Yiplex's brand lime in the standard step layout. Its 800 solid is
#BAC836 (#C6D43B in dark mode) with 850 on hover; lime-000 is the dark
moss text for both modes. 900/950 are olive and moss text.
The CSS entry registers the selectable built-in physical palettes:
@presets "standard,icons,official";
@primary "ink,gray,gold,red,blue";
@neutral "gray,sage,slate";
@custom focused "primary";
@success "jade,green";
@warning "amber,orange";
@failure "red,ruby";
@general "blue,cyan";
@teacss;Repeated/imported lists union and sort. They enable choices without changing
defaults; removing every list for a role clears its choices. Only Standard's
original built-in physical palette names are candidates. Overrides through
@custom or theme.colors keep that identity; custom names remain available to
ordinary color utilities but cannot become role candidates. Enabled palettes
need all 12 effective steps and no direct/transitive role references, including
mode overrides, preflightBase, and variable fallbacks. Candidate registration
has no public Theme field; programmatic consumers load the CSS entry with
resolveEntryConfig().
<html data-css-primary="blue" data-css-neutral="slate" data-css-appearance="dark">
<body><p class="text-color:primary-900">Global blue / slate / dark</p></body>
</html>Only html role attributes, data-css-appearance and configured mode aliases select
colors. Descendants inherit; the same markers on descendants have no effect.
Missing, empty, unknown or undeclared role values use configured defaults.
Focused defaults to final global neutral while preserving explicit default
step overrides. The site may bind focus once with @custom focused "primary";,
"neutral" or a complete palette. @focused lists are removed; data-css-focused
has no effect. The other six roles retain their selectable lists.
Changing root attributes switches registered candidates without regenerating CSS.
@dark / @light inspect html for every final target, including pseudo-elements.
Without a valid explicit mode, neither condition activates, even when defaultMode is
dark; defaultMode names the base table. OS conditions remain independent media
queries. modeSelectors: { dark: ".night", light: ".day" } adds optional aliases
on html. Canonical data-css-appearance wins even when empty/invalid; conflicting
aliases select neither mode. Aliases are never implicit. Mode names preserve
case, spaces and punctuation; empty names, NUL and unpaired surrogates are invalid.
Theme output uses zero-specificity ordinary html selectors: base variables once,
mode differences, then independent role mappings. Each role/candidate mapping is
emitted once, without multiplying it by modes or other roles. preflightRoot
adds explicit re-declaration targets that always use html selections; it does not
enable local colors. On-demand preflight retains the consumed dependency closure
through all registered candidates and effective modes. With preflight disabled,
root mode conditions still work but the application supplies variables.
Global @custom sets scales and colors; @custom dark changes colors only.
Whole-theme variants and role scheme blocks are unsupported. Color selection
never resets inherited spacing or changes radius/shadow geometry.
Root distances
--spacing is the only distance knob; default named steps derive from it:
--spacing: 0.25rem;
--spacing-1x: var(--spacing);
--spacing-5x: calc(var(--spacing) * 6);Override it on html, such as $spacing:0.125rem@sm, to change numeric and
default named spacing; named multipliers remain 1,2,3,4,6,8,10,12,16. For a
whole-page size change use browser zoom or the root font size: every scale is
rem-based. --scaling was removed in 0.7.2 for that reason. Explicit named
overrides remain final values; reference --spacing to make a custom step follow.
Variables resolve where declared, so a descendant-only override does not
recompute inherited root spacing. --T-radius-factor (default 1) is the only
shape knob. Data-dense screens use explicit density classes, not multipliers;
see the Skill's density rules.
The default sans stack is IBM Plex Sans followed by PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Source Han Sans SC and Noto Sans CJK SC; mono ends with PingFang SC,
Microsoft YaHei and Noto Sans CJK SC before monospace.
Relationship markers
data-css-group enables anonymous ancestor conditions such as @group-hover;
data-css-peer enables preceding-sibling conditions such as @peer-checked.
Named values are ASCII-whitespace-separated lists: data-css-group="card panel"
also enables @group-hover/card and @group-hover/panel. Presence always
matches anonymous conditions; an empty value enables only anonymous ones.
Names are case-sensitive. Group remains descendant matching, peer uses ~.
Root language conditions
<html lang="zh">
<body><p lang="en" class="p:4 p:10@lang-zh">...</p></body>
</html>@lang-<tag> compares the complete root html:root[lang] value, ignoring ASCII
case without trimming. Here @lang-zh matches and @lang-en does not.
zh, zh-CN, and zh-Hans are independent: @lang-zh does not match a root
value of zh-CN. Local lang and descendant color markers do not override the root
language.
Root elements, descendants, targets, and pseudo-elements share this condition.
Use BCP 47 tags: zh, zh-CN, zh-Hans, zh-Hans-CN, es-419,
de-1996, and en-u-ca-gregory. Extlang, private-use (x-project), and
grandfathered (i-klingon) forms are also supported. Validation checks structure,
not registry membership; it does not canonicalize aliases or infer parent tags.
Tag case is unrestricted: @lang-zh-CN and @lang-zh-cn match identically.
Non-ASCII characters, malformed subtags, underscores, whitespace, wildcards,
and escapes are unmatched. The lowercase lang- prefix is reserved: rename custom
breakpoint keys using it, including invalid language forms. Existing @max-<name>
syntax is unchanged.
@!lang-en also matches missing/empty root language. Conditions compose with
states, relations, queries, modes, targets, shortcuts, and @apply using AND.
Conflicting languages never match; repetition follows ordinary suffix behavior.
The language guard contributes 0,1,0 specificity before existing wrappers;
space-* / divide-* keep their zero-specificity selectors.
Changing root lang updates generated CSS without runtime listeners or
regeneration, including with preflight disabled. Keep complete tokens in scanned
sources or safelists; do not escape @ in HTML/JSX class strings. Each HTML iframe
uses its own root; propagation across Shadow DOM or independent XML documents
is unsupported. Applications own translation, dir, and SSR's initial language.
Class merging
import { cn } from "@teacss/classes";
cn("p:4 d:flex", "p:8"); // "d:flex p:8"
cn("p:4", "p:invalid"); // "p:invalid"
cn("p:2@lang-en", "p:4@lang-en"); // "p:4@lang-en"
cn("p:2@lang-ZH", "p:4@lang-zh"); // "p:4@lang-zh"cn uses fixed Standard and Icons rules. Install and import @teacss/classes
directly for runtime class composition.
Merge uses prefix shape and ordered conditions, ignoring ASCII language-tag
case while preserving each winning token's spelling. It does not validate values
or condition support: malformed suffixes can merge but emit no CSS.
Unknown declarations merge
only by their own prefix; application shortcuts have no cross-prefix footprint.
Native shorthands are emitted before their component overrides. For example,
cn("border:[1px_solid_red]", "border-t-width:4") retains both classes and
produces a 4px top border. Reversing those arguments leaves only the shorthand.
The same ordering covers place/gap/columns, text families and corner shapes;
explicit display and overflow can override line-clamp's corresponding declarations.
animation-composition: stays independent of animation: in either input order;
merging a named animation preserves its additive or accumulating behavior.
Source and condition boundaries
space-* conditions, including conditions on enclosing shortcuts and reverse
activators, target the container. Conditional spacing overrides its default even
when the requested distance is smaller; child-local margin utilities still win
against the zero-specificity container rules.
Named container queries require a valid container name. CSS-wide keywords,
default, none, and, or, and not are rejected, including escaped or
case-varied spellings, rather than becoming anonymous queries.
The ! suffix marks the declaration important even when its value contains the
literal text !important. Quoted text and function arguments are not priorities.
The default :css: transformer decodes static JS/TS string escapes once before
compilation. JSX/HTML attribute values and String.raw retain their CSS escapes.
Dynamic template literals are left unchanged; use complete static strings for
compiled classes. Kept unknown classes are escaped back into the original JS quote.
Value and composition boundaries
- Logical border width/style pairs accept two arbitrary components, such as
border-inline-width:[1px_2px]; physical x/y and single-edge forms accept one. […]keeps structurally valid arbitrary CSS, includinganimation-name:[var(--name)]. It does not guarantee browser support.- Custom-variable values and
font-paletteuse the shared structural checks; failed bracket/variable decoding cannot fall back to unchecked raw CSS. - A configured
DEFAULTkey references its unsuffixed variable:p:DEFAULTreads--spacing, including through negative spacing. Applications supply these scale variables when preflight is disabled. - Named shadows use global templates and support local color overrides.
shadow:$shadow-cardreads a complete raw variable without recoloring;box-shadow:[…]replaces the whole composed surface.box-shadow:noneclears that surface, including shadow outlines, when it wins the cascade;shadow:noneandinset-shadow:noneclear only their respective slots. - Named animations quote their decoded CSS names to avoid shorthand keyword
ambiguity. Numeric zero iteration counts remain zero.
animation:spinrotates at1s linear infinite;animation:spinnerfades a spinner leaf from opacity1to.25at800ms linear infinite. Duration, timing, and iteration utilities override these defaults. Gate decorative motion with@motion-safeor stop it withanimation:none@motion-reduce. - Bare background/mask sizes cannot be negative.
- Border-spacing axis variables do not inherit into nested tables.
- Gradient properties are registered on demand, retaining shared fallbacks and neutral additive-mask layers without registering unrelated families.
Explicit activators
Standard utilities all use property:value. The following activators accept
exactly on, with ordinary importance and suffix conditions:
mask-g-linear:on mask-g-radial:on mask-g-conic:on
space-x-reverse:on space-y-reverse:on space-inline-reverse:on space-block-reverse:onUse divide-x-width:1 / divide-y-width:1 for 1px child dividers. The former
nine bare spellings emit nothing; bare names are reserved for registered
application or Official shortcuts. Removing an activator restores its default;
off, true, variable, arbitrary, and CSS-wide values are unsupported.
Pre-1.0. Keep tests and documentation aligned when changing vocabulary.
Data dictionaries and reports
@custom office "indigo"; registers 13 alias steps (including 000 with white
fallback), preserving on-demand reference closure and global mode values.
Write bg-color:office-900 or fill-color:office-900/50@hover; keep physical
families in the dictionary. Targets require 12 effective steps and cannot be
aliases; focused alone may follow primary/neutral. Per-step and mode blocks
remain available for overrides. Config owns validation and editor source ranges.
State needs an action or judgement and text. Category uses a fixed dictionary name/order; quantity uses a sequential meaning, direction a site-defined pair. Reports share those meanings, numbers, units, global mode and font. They use one neutral-100 surface, neutral-500 section lines, prose-hans paragraphs, 32px tables outside prose and directly labelled charts, without primary, controls or compact.
