Class naming
Configure semantic, hashed, or atomic-style class names for styles.create, styles.class, and styles.component
Class naming
By default, typestyles emits readable semantic class names: button-base, card-elevated, button-intent-primary. You can switch to hashed or hash-only names for smaller strings, fewer accidental collisions across packages, or closer parity with CSS-in-JS tools that minify class names.
Naming applies to:
styles.createstyles.classstyles.component(single-part components and multipartslots)
It does not change @typestyles/props utility naming; that package uses its own createProps namespace pattern.
Quick start
Call configureClassNaming once at app or package entry (for example your design system index.ts or root main.tsx) before modules register styles:
import { configureClassNaming } from 'typestyles';
configureClassNaming({
mode: 'hashed',
prefix: 'ds',
scopeId: '@acme/design-system',
});
Then existing styles.create / styles.component calls keep the same TypeScript API; only the emitted class strings and generated selectors change.
API
configureClassNaming(options)
Merges into the current global config (partial updates are allowed).
| Option | Type | Default | Description |
|---|---|---|---|
mode |
'semantic' | 'hashed' | 'atomic' |
'semantic' |
How class strings are built (see below). |
prefix |
string |
'ts' |
Leading segment for hashed/atomic output and for styles.hashClass. |
scopeId |
string |
'' |
Optional id (package name, app name) mixed into the hash input so two packages can reuse the same logical namespace without sharing the same class string. |
getClassNamingConfig()
Returns a read-only snapshot of the active config (useful for debugging).
resetClassNaming()
Restores defaults. Intended for tests so one suite cannot leak naming mode into another; not something you typically call in application code.
Modes
semantic (default)
Human-readable, stable names derived from the namespace and variant segment:
styles.create('card', { base: { … } })→card-basestyles.component('button', { … })→button-base,button-intent-primary, etc.- Components with
slots→{namespace}-{slot},{namespace}-{slot}-{dimension}-{option}, etc.
hashed
Deterministic names of the form {prefix}-{namespace-slug}-{hash}. The hash is computed from (when set) scopeId, the namespace, a variant segment (e.g. base, intent-primary, root-compound-0), and the serialized style object for that rule. Identical definitions produce identical class strings.
Use this when you want shorter, scoped names while still recognizing the namespace in DevTools.
atomic
{prefix}-{hash} only—no namespace slug in the string. Same hash inputs as hashed, so behavior is equally deterministic.
This mode is a prototype for hash-only ergonomics: each component rule is still one class per chunk of CSS (the same as today), not one utility class per CSS declaration. True per-property atomic output is a separate roadmap area; for Tailwind-style utilities, use @typestyles/props.
styles.hashClass
styles.hashClass(styles, label?) always emits a hashed class from the style object. It uses the configured prefix. If scopeId is non-empty, it is included in the hash input so scoped packages do not collide.
When scopeId is the default empty string, the hash input matches the previous behavior (properties only, plus label handling), so existing class strings stay stable if you only adopt prefix or other naming modes for create / component.
Monorepos and scopeId
Two packages might both use styles.create('button', …) or styles.component('button', …). With semantic mode, you rely on distinct namespaces. With hashed / atomic, set a different scopeId per package (for example the npm package name) so identical style objects in different packages do not map to the same class string.
SSR and entry order
Naming is global for the loaded bundle. Ensure configureClassNaming runs before any module that calls styles.create, styles.class, or styles.component during that load. In SSR, the server bundle should apply the same configuration as the client so class names and injected CSS match.
Testing
If tests call configureClassNaming, reset in beforeEach (or afterEach) so other tests keep the default:
import { resetClassNaming } from 'typestyles';
import { reset } from 'typestyles';
beforeEach(() => {
reset();
resetClassNaming();
});
If you assert on class strings under hashed or atomic, prefer stable snapshots or assert on substrings (prefix, absence of semantic segments) rather than hard-coding full hashes unless you fix scopeId and styles.
See also Testing.
Related
- Styles —
styles.create,styles.class,compose,withUtils - Components —
styles.componentandslots - Atomic CSS Utilities —
@typestyles/props(separate naming scheme) - API Reference — export list