Vitus Labs
Styler

API Reference

Complete API reference for @vitus-labs/styler.

Exports

import {
  css,
  styled,
  keyframes,
  createGlobalStyle,
  ThemeProvider,
  useTheme,
  useCSS,
  sheet,
  createSheet,
} from '@vitus-labs/styler'

import type {
  CSSResult,
  Interpolation,
  StyledOptions,
  StyleSheetOptions,
  DefaultTheme,
} from '@vitus-labs/styler'

Styling Functions

css

css(strings: TemplateStringsArray, ...values: Interpolation[]): CSSResult

Tagged template for composable CSS fragments. Returns a lazy CSSResult that resolves when consumed by styled, useCSS, or toString().

styled

styled(tag: string | ComponentType, options?: StyledOptions):
  (strings: TemplateStringsArray, ...values: Interpolation[]) => ForwardRefComponent

Also available as styled.div, styled.button, etc. via Proxy.

Creates a React component that:

  1. Resolves interpolations with component props + theme
  2. Hashes the resolved CSS string (FNV-1a, base-36)
  3. Injects the style rule if not already present (dedup via cache)
  4. Applies the generated class name (vl-<hash>) to the element
  5. Filters $-prefixed transient props from DOM output
  6. Forwards ref to the underlying element

Static path (no function interpolations): CSS resolved once at creation time, zero per-render overhead. Dynamic path (function interpolations): CSS resolved per render, useRef cache avoids rehashing when unchanged.

keyframes

keyframes(strings: TemplateStringsArray, ...values: Interpolation[]): KeyframesResult

Returns a KeyframesResult with:

  • .name: string — Animation name (vl-kf-<hash>)
  • .toString(): string — Returns .name for use in CSS interpolations

The @keyframes rule is injected immediately and synchronously. No dynamic interpolation support (keyframes are always static). Deduplicated by animation name.

createGlobalStyle

createGlobalStyle(strings: TemplateStringsArray, ...values: Interpolation[]): FC

Returns a React component. When mounted:

  • Static path: CSS injected once at creation; component renders nothing on client
  • Dynamic path: CSS resolved per render with { ...props, theme }, injected via useInsertionEffect
  • SSR: Renders <style precedence="low"> for FOUC-free delivery
  • CSS is unscoped (no .vl-* class wrapper)
  • Deduplicated by hash of resolved CSS

Theme

ThemeProvider

ThemeProvider: FC<{ theme: DefaultTheme & Record<string, unknown>; children: ReactNode }>

React Context provider that makes a theme object available to all descendant styled components via props.theme.

useTheme

useTheme<T extends Theme = Theme>(): T

Hook to read the current theme from the nearest ThemeProvider. Returns {} if called outside any provider.

Hooks

useCSS

useCSS(template: CSSResult, props?: Record<string, any>, boost?: boolean): string

Resolves a CSSResult to a class name string. Theme is automatically merged from context.

  • Same injection mechanism as styled() (useInsertionEffect + dedup cache)
  • props — Additional props passed to function interpolations
  • boost — Double the selector for raised specificity
const highlight = css`
  color: red;
  font-weight: bold;
`

function Label({ children }) {
  const className = useCSS(highlight)
  return <span className={className}>{children}</span>
}

StyleSheet API

sheet

import { sheet } from '@vitus-labs/styler'

The global StyleSheet singleton used by all styled components and hooks. On the client, manages a single <style data-vl> DOM element.

createSheet

createSheet(options?: StyleSheetOptions): StyleSheet

Creates an isolated StyleSheet instance. Use for SSR per-request isolation.

Sheet Methods

MethodReturnsDescription
insert(cssText, boost?)stringInject scoped CSS, returns class name. Dedup via cache.
insertKeyframes(name, body)voidInject @keyframes rule. Dedup by name.
insertGlobal(cssText)voidInject unscoped global CSS. Dedup by hash.
getClassName(cssText)stringCompute class name without injecting (render-phase safe).
prepare(cssText, boost?){ className, rules }Compute class + full rule text without injecting. For SSR <style precedence>.
getStyleTag()stringComplete <style data-vl="">...</style> tag with collected CSS. Manual mode only — empty under the default React 19 precedence path.
getStyles()stringRaw collected CSS rules string (no <style> wrapper). Manual mode only — empty under the default precedence path.
has(className)booleanO(1) cache lookup for dedup check.
reset()voidClear ssrBuffer + cache. Call between server requests.
clearCache()voidClear the dedup cache plus resolve-side caches (insertCache, prepareCache, normCache). Keeps already-injected DOM rules.
clearAll()voidEverything clearCache() does, plus clear ssrBuffer, remove all DOM rules, and fire registered clearCallbacks so consumers (e.g. styled.ts's static caches) reset. Full HMR reset.
cacheSize (getter)numberCurrent number of cached entries.

SSR — Default (React 19 <style precedence>)

On the server, styled components render <style href={className} precedence="medium"> elements alongside their DOM output. React 19's streaming renderer hoists these to <head> automatically, dedupes by href, and orders them by precedence. This is the default path — no manual style collection needed.

Precedence levels:

  • medium — Component styles (from styled() and useCSS())
  • low — Global styles (from createGlobalStyle())

Hydration

On the client, StyleSheet.mount():

  1. Looks for an existing <style data-vl=""> element. If present, reuses its CSSOM sheet and seeds the dedup cache from any rules already inside it. Otherwise, creates a fresh empty <style data-vl="">.
  2. Walks every <style data-precedence][data-href]> tag React emitted during SSR and pre-populates the dedup cache with those data-href values. This is what prevents the first useInsertionEffect from re-injecting a rule that's already in the DOM under a precedence tag.
  3. On subsequent renders, useInsertionEffect cache-hits and skips insertion for any class already known.

Manual SSR Mode (legacy / streaming)

For pipelines that need to collect CSS by hand (e.g. older SSR runtimes that don't process <style precedence>), opt in by calling sheet.insert() / sheet.insertGlobal() outside of render:

import { createSheet } from '@vitus-labs/styler'

// Per-request isolation
const sheet = createSheet()

// Manually push rules into the buffer (NOT done automatically — the default
// precedence path leaves ssrBuffer empty to avoid duplicate emission).
sheet.insert('color: red;')

// Render your app...
const html = renderToString(<App />)

// Extract collected CSS
const styleTag = sheet.getStyleTag()
// → '<style data-vl="">...all CSS rules...</style>'

// Or raw CSS for custom injection
const css = sheet.getStyles()

// Send to client
response.send(`<!DOCTYPE html><html><head>${styleTag}</head><body>${html}</body></html>`)

// Reset for next request
sheet.reset()

Under the default precedence path, getStyleTag() and getStyles() return empty output because the buffer was never populated — every rule travelled through React 19's collected <style precedence> tags instead.

@layer Support

Wrap all scoped CSS in a CSS @layer:

const sheet = createSheet({ layer: 'components' })
// All CSS: @layer components { .vl-abc { ... } }

Cache Eviction

  • Default max cache size: 10,000 entries
  • When exceeded, oldest 10% of entries are evicted (Map insertion order)
  • Configurable via createSheet({ maxCacheSize: 5000 })

Types

CSSResult

class CSSResult {
  readonly strings: TemplateStringsArray
  readonly values: Interpolation[]
  toString(): string
}

The return type of css(). A lazy container storing template strings and interpolation values. Resolves to CSS when consumed.

Interpolation

type Interpolation =
  | string
  | number
  | boolean
  | null
  | undefined
  | CSSResult
  | Interpolation[]
  | ((props: {
      theme?: DefaultTheme & Record<string, any>
      [key: string]: any
    }) => Interpolation)

Values accepted in tagged template interpolation positions. The type is recursive — arrays can contain any interpolation type, functions can return any interpolation type.

StyledOptions

interface StyledOptions {
  /** Custom prop filter. Return true to forward the prop to the DOM element. */
  shouldForwardProp?: (prop: string) => boolean
  /** Double the class selector for raised specificity (0,2,0). */
  boost?: boolean
}

StyleSheetOptions

interface StyleSheetOptions {
  /** Maximum cached rules before eviction (default: 10000). */
  maxCacheSize?: number
  /** CSS @layer name to wrap scoped rules in. */
  layer?: string
}

DefaultTheme

interface DefaultTheme {}

Empty by default. Extend via module augmentation for type-safe theme access.

Architecture

Resolution Flow

  1. Definitioncss or styled captures template strings and interpolation references
  2. Resolution — At render time, function interpolations called with { ...props, theme }
  3. Normalization — Single-pass cleanup: strip comments, collapse whitespace, remove redundant semicolons
  4. Hashing — FNV-1a hash of normalized CSS → base-36 string
  5. Deduplication — Cache lookup; if hash exists, reuse class name
  6. At-rule splitting — Extract @media/@supports/@container to top-level rules
  7. InjectionuseInsertionEffect on client, <style precedence> on SSR
  8. Application — Generated class name (vl-<hash>) applied to element

Hash Algorithm

FNV-1a (32-bit) with base-36 encoding:

// Streaming API (for incremental hashing)
import { HASH_INIT, hashUpdate, hashFinalize } from '@vitus-labs/styler'

let h = HASH_INIT        // 2166136261 (FNV offset basis)
h = hashUpdate(h, 'abc') // Feed string segment
h = hashUpdate(h, 'def') // Continue feeding
const result = hashFinalize(h) // → base-36 string

// One-shot API
import { hash } from '@vitus-labs/styler'
hash('abcdef') // Same result as streaming above

Client Injection

A single <style data-vl> element is used for all injected rules. useInsertionEffect ensures rules are inserted synchronously before layout/paint, preventing FOUC.

Performance

MetricValue
Bundle size~10.3 KB gzipped (~32 KB minified)
Static pathO(1) per render
Dynamic pathO(n) per render (n = CSS string length)
Cache lookupO(1) via Map
CSS insertionO(1) amortized (CSSOM insertRule)
DOM elements1 shared <style> for all components

Auto-Generated Types

StyledOptions

Prop

Type

StyleSheetOptions

Prop

Type

CSSResult

Prop

Type

On this page