---
title: "CSS Styling"
description: "Hono's CSS styling utilities provide a unified, zero-dependency mechanism for authoring, scoping, hashing, and injecting styles across both server-side template rendering and client-side browser ru..."
last_updated: "2026-10-12T08:14:12.740617+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/css-styling"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/jsx/dom/css.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts)
- [src/helper/css/common.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts)
- [src/jsx/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/components.ts)
- [src/helper/css/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts)
- [src/jsx/jsx-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts)
- [src/jsx/dom/render.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/render.ts)
- [src/jsx/streaming.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/streaming.ts)
- [src/middleware/secure-headers/secure-headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/secure-headers/secure-headers.ts)
- [src/jsx/intrinsic-element/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/intrinsic-element/components.ts)
- [src/helper/streaming/sse.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/streaming/sse.ts)
- [src/jsx/dom/intrinsic-element/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/intrinsic-element/components.ts)
- [src/jsx/hooks/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/hooks/index.ts)
- [src/middleware/jsx-renderer/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jsx-renderer/index.ts)
- [src/jsx/base.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts)
- [src/jsx/utils.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/utils.ts)
- [src/jsx/dom/client.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/client.ts)
- [package.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/package.json)
- [src/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/context.ts)
- [src/jsx/jsx-dev-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-dev-runtime.ts)
- [src/adapter/cloudflare-pages/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-pages/handler.ts)
- [src/jsx/dom/server.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts)
- [src/jsx/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/index.ts)
- [src/jsx/dom/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/index.ts)
- [src/jsx/dom/jsx-dev-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-dev-runtime.ts)
- [src/helper/ssg/middleware.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts)
- [src/hono-base.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/hono-base.ts)
- [src/utils/html.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/html.ts)
- [src/jsx/dom/jsx-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-runtime.ts)
- [src/middleware/context-storage/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/context-storage/index.ts)
- [src/jsx/intrinsic-element/common.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/intrinsic-element/common.ts)
</details>

## Overview

Hono's CSS styling utilities provide a unified, zero-dependency mechanism for authoring, scoping, hashing, and injecting styles across both server-side template rendering and client-side browser runtimes. By bridging server-side context tracking with dynamic CSSOM rule insertion in `hono/jsx/dom`, the CSS helper enables template literal-based style declarations (`css`, `cx`, `keyframes`, and `viewTransition`) that automatically normalize identifiers, minify CSS strings, and manage critical style injection without external build steps.

Sources: [src/jsx/dom/css.ts:1-143](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L1-L143), [src/helper/css/common.ts:1-178](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L1-L178), [src/helper/css/index.ts:1-194](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L1-L194)

## CSS Helper Public API

### Core Functions and Signatures

The public API surface for Hono's CSS styling utilities is exported from `src/helper/css/index.ts` and `src/helper/css/common.ts`. It exposes functions like `css`, `cx`, `keyframes`, `viewTransition`, and `rawCssString`, alongside factory functions such as `createCssContext` for customizing identifiers and context behavior across both server and browser environments.

The styling helper functions accept template strings and variable values, producing hashed class name objects, view transitions, or keyframe identifiers. The `createCssContext` function returns an object containing these bound helpers along with a `Style` component for rendering stylesheet elements with optional nonces.

| Function | Signature | Description |
| :--- | :--- | :--- |
| `css` | `(strings: TemplateStringsArray, ...values: CssVariableType[]): Promise<string>` | Generates a scoped hashed class name and registers the corresponding CSS rule. |
| `cx` | `(...args: (CssClassName \| Promise<string> \| string \| boolean \| null \| undefined)[]): Promise<string>` | Combines multiple class names or conditional style arguments into a single string. |
| `keyframes` | `(strings: TemplateStringsArray, ...values: CssVariableType[]): CssClassNameCommon` | Defines scoped `@keyframes` animation names and styles. |
| `viewTransition` | `(strings: TemplateStringsArray, ...values: CssVariableType[]): Promise<string>` | Creates view transition styles or helper wrappers. |
| `rawCssString` | `(value: string): CssEscapedString` | Wraps raw CSS text so it is inserted without escaping or modifications. |

Sources: [src/helper/css/common.ts:32-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L32-L36), [src/helper/css/index.ts:38-60](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L38-L60), [src/helper/css/index.ts:71-193](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L71-L193)

```typescript
export interface CssClassName {
  [SELECTOR]: string
  [CLASS_NAME]: string
  [STYLE_STRING]: string
  [SELECTORS]: CssClassName[]
  [EXTERNAL_CLASS_NAMES]: string[]
}
```

Sources: [src/helper/css/common.ts:13-19](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L13-L19)

> [!NOTE]
> `createCssContext` is an experimental feature whose API configuration parameters and return types may change in future releases. It accepts an options object containing an `id`, optional `classNameSlug` transformer, and `onInvalidSlug` callback.

Sources: [src/helper/css/index.ts:62-79](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L62-L79)

## Class Name Hashing and Normalization

### Deterministic Style Hashing and Minification

Hono generates deterministic class name hashes using an internal `toHash` function adapted from goober. This algorithm processes the input style string character by character, computing an integer accumulator via multiplication and character code addition (`str.charCodeAt(i++)`), and prepending the `css-` prefix to produce identifiers like `css-1234567890`.

```typescript
const toHash = (str: string): string => {
  let i = 0,
    out = 11
  while (i < str.length) {
    out = (101 * out + str.charCodeAt(i++)) >>> 0
  }
  return 'css-' + out
}
```
Sources: [src/helper/css/common.ts:43-50](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L43-L50)

To keep emitted style rules compact, the `minify` function strips redundant whitespace, comments, and unnecessary separators using regular expressions matched via `minifyCssRe`. It preserves quoted strings defined by double or single quotes while collapsing spaces, head/tail whitespace, multi-line/single-line comments, and surrounding whitespace around structural tokens like `{`, `}`, `:`, `,`, and `;`.

```typescript
const cssStringReStr: string = [
  '"(?:(?:\\\\[\\s\\S]|[^"\\\\])*)"', // double quoted string

  "'(?:(?:\\\\[\\s\\S]|[^'\\\\])*)'", // single quoted string
].join('|')
const minifyCssRe: RegExp = new RegExp(
  [
    '(' + cssStringReStr + ')', // $1: quoted string

    '(?:' +
      [
        '^\\s+', // head whitespace
        '\\/\\*.*?\\*\\/\\s*', // multi-line comment
        '\\/\\/.*\\n\\s*', // single-line comment
        '\\s+$', // tail whitespace
      ].join('|') +
      ')',

    '\\s*;\\s*(}|$)\\s*', // $2: trailing semicolon
    '\\s*([{};:,])\\s*', // $3: whitespace around { } : , ;
    '(\\s)\\s+', // $4: 2+ spaces
  ].join('|'),
  'g'
)

export const minify = (css: string): string => {
  return css.replace(minifyCssRe, (_, $1, $2, $3, $4) => $1 || $2 || $3 || $4 || '')
}
```
Sources: [src/helper/css/common.ts:75-102](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L75-L102)

### Label Extraction and Identifier Validation

Template literal styles can include an optional leading comment label (e.g., `/* my-button */`) to aid readability and custom class name generation. The `buildStyleString` helper extracts this label using a regular expression match on `strings[0]`. The `normalizeLabel` function then trims leading/trailing whitespace and replaces inner whitespace sequences with hyphens.

```typescript
const normalizeLabel = (label: string): string => {
  return label.trim().replace(/\s+/g, '-')
}
```
Sources: [src/helper/css/common.ts:52-54](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L52-L54), [src/helper/css/common.ts:142-142](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L142-L142)

Custom class names generated via `ClassNameSlug` callbacks are validated against CSS identifier rules before use. The `isValidClassName` validator ensures names match standard identifier syntax, while `isValidKeyframeName` additionally checks that animation names do not collide with CSS-wide reserved keywords.

| Validator Function | Regex / Rule | Description |
| :--- | :--- | :--- |
| `isValidClassName` | `/^-?[_a-zA-Z][_a-zA-Z0-9-]*$/` | Validates that a string is a safe CSS identifier. |
| `isValidKeyframeName` | `isValidClassName(name) && !RESERVED_KEYFRAME_NAMES.has(...)` | Validates class names and checks against reserved animation keywords. |

```typescript
const isValidClassName = (name: string): boolean => /^-?[_a-zA-Z][_a-zA-Z0-9-]*$/.test(name)

// CSS-wide keywords that are invalid as @keyframes names per the spec
const RESERVED_KEYFRAME_NAMES = new Set([
  'default',
  'inherit',
  'initial',
  'none',
  'revert',
  'revert-layer',
  'unset',
])
const isValidKeyframeName = (name: string): boolean =>
  isValidClassName(name) && !RESERVED_KEYFRAME_NAMES.has(name.toLowerCase())
```
Sources: [src/helper/css/common.ts:56-69](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L56-L69)

> [!WARNING]
> If a custom `ClassNameSlug` returns an invalid slug that fails `isValidClassName` or contains a reserved keyframe name, Hono invokes `onInvalidSlug` (which defaults to `console.warn`) and falls back to the default hash-based class name.

Sources: [src/helper/css/common.ts:71-73](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L71-L73)

## Server Critical CSS Style Component

### Context Creation and State Tracking

The `createCssContext` factory initializes request-scoped tracking maps and generators needed during server-side template rendering. It maintains a `WeakMap` named `contextMap` to store used class name data per render context, alongside a `nonceMap` tracking Content Security Policy nonces.

```typescript
export const createCssContext = ({
  id,
  classNameSlug,
  onInvalidSlug,
}: {
  id: Readonly<string>
  classNameSlug?: ClassNameSlug
  onInvalidSlug?: OnInvalidSlug
}): DefaultContextType => {
  const [cssJsxDomObject, StyleRenderToDom] = createCssJsxDomObjects({ id })

  const contextMap: WeakMap<object, usedClassNameData> = new WeakMap()
  const nonceMap: WeakMap<object, string | undefined> = new WeakMap()

```
Sources: [src/helper/css/index.ts:71-84](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L71-L84)

When a style is referenced during JSX rendering, `addClassNameToContext` checks whether the render context is registered in `contextMap`. If absent, it initializes a tuple containing two records: `toAdd` (class names queued for emission) and `added` (class names already output). It inspects the primary selector and nested sub-selectors, queuing unrendered definitions and attaching an asynchronous HTML escaped callback to handle final insertion.

```typescript
const addClassNameToContext: HtmlEscapedCallback = ({ context }) => {
  if (!contextMap.has(context)) {
    contextMap.set(context, [{}, {}])
  }
  const [toAdd, added] = contextMap.get(context) as usedClassNameData
  let allAdded = true
  if (!added[cssClassName[SELECTOR]]) {
    allAdded = false
    toAdd[cssClassName[SELECTOR]] = cssClassName[STYLE_STRING]
  }
  cssClassName[SELECTORS].forEach(
    ({ [CLASS_NAME]: className, [STYLE_STRING]: styleString }) => {
      if (!added[className]) {
        allAdded = false
        toAdd[className] = styleString
      }
    }
  )
  if (allAdded) {
    return
  }

  return Promise.resolve(raw('', [appendStyle]))
}
```
Sources: [src/helper/css/index.ts:123-146](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L123-L146)

> [!NOTE]
> `usedClassNameData` is defined as a tuple type `[Record<string, string>, Record<string, true>]`, cleanly separating active styles awaiting buffer flushing from global lookup records tracking already injected rules.

Sources: [src/helper/css/index.ts:33-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L33-L36)

### Style Emission and DOM Insertion

The `Style` component outputs the initial `<style>` element using Hono's `raw` helper, configured with a designated element identifier and an optional cryptographic `nonce`. Its callback registers the nonce against the active render context.

```typescript
const Style: StyleType = ({ children, nonce } = {}) =>
  raw(
    `<style id="${id}"${nonce ? ` nonce="${nonce}"` : ''}>${
      children ? (children as unknown as CssClassName)[STYLE_STRING] : ''
    }</style>`,
    [
      ({ context }) => {
        nonceMap.set(context, nonce)
        return undefined
      },
    ]
  )
```
Sources: [src/helper/css/index.ts:182-193](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L182-L193)

During HTML buffering and serialization, `appendStyle` iterates over accumulated class names, marking each as added. It formats pseudo-global selectors directly or wraps standard class names in period prefixes and curly braces. It then checks if the output buffer already contains an inline style element matching the configured identifier using `replaceStyleRe`.

```typescript
const replaceStyleRe = new RegExp(`(<style id="${id}"(?: nonce="[^"]*")?>.*?)(</style>)`)

const newCssClassNameObject = (cssClassName: CssClassNameCommon): Promise<string> => {
  const appendStyle: HtmlEscapedCallback = ({ buffer, context }): Promise<string> | undefined => {
    const [toAdd, added] = contextMap.get(context) as usedClassNameData
    const names = Object.keys(toAdd)

    if (!names.length) {
      return
    }

    let stylesStr = ''
    names.forEach((className) => {
      added[className] = true
      stylesStr += className.startsWith(PSEUDO_GLOBAL_SELECTOR)
        ? toAdd[className]
        : `${className[0] === '@' ? '' : '.'}${className}{${toAdd[className]}}`
    })
    contextMap.set(context, [{}, added])

    if (buffer && replaceStyleRe.test(buffer[0])) {
      buffer[0] = buffer[0].replace(replaceStyleRe, (_, pre, post) => `${pre}${stylesStr}${post}`)
      return
    }
```
Sources: [src/helper/css/index.ts:85-108](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L85-L108)

If an existing style element is located within the buffer, `appendStyle` injects the accumulated CSS string directly into the tag via regular expression replacement. Otherwise, it generates an inline script block appending the rules to `document.querySelector('#id').textContent` (incorporating the CSP nonce if present) and prepends it to the response buffer.

```typescript

  const nonce = nonceMap.get(context)
  const appendStyleScript = `<script${
    nonce ? ` nonce="${nonce}"` : ''
  }>document.querySelector('#${id}').textContent+=${JSON.stringify(stylesStr)}</script>`

  if (buffer) {
    buffer[0] = `${appendStyleScript}${buffer[0]}`
    return
  }

  return Promise.resolve(appendStyleScript)
}
```
Sources: [src/helper/css/index.ts:109-121](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L109-L121)

> [!WARNING]
> If a style element matching the context identifier is not present in the render buffer when styles flush, Hono falls back to injecting an inline script containing a DOM query selector fallback. This requires client-side JavaScript execution to populate the stylesheet dynamically.

Sources: [src/helper/css/index.ts:110-121](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts#L110-L121)

## Client DOM Stylesheet Insertion

### Stylesheet Discovery and Rule Splitting

The browser-side CSS subsystem initializes via `createCssJsxDomObjects`, which accepts an identifier to locate the corresponding `<style>` element in the DOM tree. The `findStyleSheet` utility queries `document.querySelector<HTMLStyleElement>(\`style#${id}\`)?.sheet` and initializes a `Set` on the `CSSStyleSheet` instance under the property name `addedStyles` to track injected class names and prevent duplicate rules.

```typescript
export const createCssJsxDomObjects: CreateCssJsxDomObjectsType = ({ id }) => {
  let styleSheet: CSSStyleSheet | null | undefined = undefined
  const findStyleSheet = (): [CSSStyleSheet, Set<string>] | [] => {
    if (!styleSheet) {
      styleSheet = document.querySelector<HTMLStyleElement>(`style#${id}`)
        ?.sheet as CSSStyleSheet | null
      if (styleSheet) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        ;(styleSheet as any).addedStyles = new Set<string>()
      }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return styleSheet ? [styleSheet, (styleSheet as any).addedStyles] : []
  }
```
Sources: [src/jsx/dom/css.ts:77-90](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L77-L90)

> [!NOTE]
> The dynamic rule insertion function `splitRule` parses rule text character by character, tracking nesting depth via curly braces while correctly consuming single and double quotes—including escaped backslashes—to avoid false splits inside string literals.

Sources: [src/jsx/dom/css.ts:27-66](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L27-L66)

### Runtime CSSOM Synchronization

When a client-side CSS object is evaluated via its `toString` method, it extracts its primary selector and style string, alongside any nested sub-selectors, passing them to `insertRule`. If the target stylesheet or its tracking set is not yet available in the DOM, `insertRule` defers execution via `Promise.resolve().then()`, retrying asynchronously until the element is mounted.

```typescript
const insertRule = (className: string, styleString: string) => {
  const [sheet, addedStyles] = findStyleSheet()
  if (!sheet || !addedStyles) {
    Promise.resolve().then(() => {
      if (!findStyleSheet()[0]) {
        throw new Error('style sheet not found')
      }
      insertRule(className, styleString)
    })
    return
  }

  if (!addedStyles.has(className)) {
    addedStyles.add(className)
    ;(className.startsWith(PSEUDO_GLOBAL_SELECTOR)
      ? splitRule(styleString)
      : [`${className[0] === '@' ? '' : '.'}${className}{${styleString}}`]
    ).forEach((rule) => {
      sheet.insertRule(rule, sheet.cssRules.length)
    })
  }
}
```
Sources: [src/jsx/dom/css.ts:92-113](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L92-L113)

```mermaid
flowchart TD
    A["toString() invoked on CSS object"] --> B["extract selector and style string"]
    B --> C["insertRule(className, styleString)"]
    C --> D{"sheet & addedStyles found?"}
    D -- No --> E["Promise.resolve().then() retry"]
    E --> D
    D -- Yes --> F{"addedStyles.has(className)?"}
    F -- Yes --> G["Skip (already inserted)"]
    F -- No --> H["Mark addedStyles.add(className)"]
    H --> I{"Selector starts with ':-hono-global'?"}
    I -- Yes --> J["splitRule(styleString)"]
    I -- No --> K["Format rule string with dot or @ prefix"]
    J --> L["sheet.insertRule(rule, sheet.cssRules.length)"]
    K --> L
```
Sources: [src/jsx/dom/css.ts:92-124](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L92-L124)

If `addedStyles` does not yet contain the target class name, it marks the class as added and evaluates whether the selector begins with the pseudo-global marker `:-hono-global`. Pseudo-global rules are split into individual rule blocks using `splitRule`, while standard rules are formatted into valid CSS selectors before being appended to the CSSOM via `sheet.insertRule(rule, sheet.cssRules.length)`.

```typescript
if (!addedStyles.has(className)) {
  addedStyles.add(className)
  ;(className.startsWith(PSEUDO_GLOBAL_SELECTOR)
    ? splitRule(styleString)
    : [`${className[0] === '@' ? '' : '.'}${className}{${styleString}}`]
  ).forEach((rule) => {
    sheet.insertRule(rule, sheet.cssRules.length)
  })
}
```
Sources: [src/jsx/dom/css.ts:104-112](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L104-L112)

> [!WARNING]
> If `findStyleSheet` fails to locate the target style element after an asynchronous tick during rule insertion, Hono throws an explicit `Error('style sheet not found')`, halting execution for that deferred stylesheet update.

Sources: [src/jsx/dom/css.ts:95-100](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L95-L100)

## View Transitions and Keyframes Support

### Keyframe and Animation Rule Registration

Keyframe animations and view transitions are registered through dedicated helper functions exposed on the CSS context. The `keyframes` helper processes template strings and variable injections via `keyframesCommon`, generating a structured `CssClassName` object whose selector name is prefixed with `@keyframes `. When a keyframe identifier is referenced inside standard CSS string interpolation, `buildStyleString` detects the `@keyframes ` prefix, registers the keyframe rule into the selector collection, and rewrites the interpolation value to output the extracted animation name correctly.

```typescript
} else if ((value as CssClassName)[CLASS_NAME].startsWith('@keyframes ')) {
  selectors.push(value as CssClassName)
  styleString += ` ${(value as CssClassName)[CLASS_NAME].substring(11)} `
} else {
```
Sources: [src/helper/css/common.ts:169-172](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L169-L172)

> [!NOTE]
> Keyframe names are validated against a set of reserved CSS-wide keywords (`default`, `inherit`, `initial`, `none`, `revert`, `revert-layer`, and `unset`) to prevent invalid animation identifiers from breaking stylesheet compilation.

Sources: [src/helper/css/common.ts:59-69](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L59-L69)

### View-Transition Scoping and Pseudo-Global Handling

The `viewTransition` helper constructs scoped view-transition rules or transition names via `viewTransitionCommon`. In client DOM environments, view transitions and pseudo-global rules integrate directly with rule splitting and CSSOM insertion. When selectors start with the pseudo-global marker `:-hono-global`, Hono bypasses standard dot-prefixing and decomposes the rule string into individual sub-rules via `splitRule` before inserting them into the target stylesheet.

```typescript
;(className.startsWith(PSEUDO_GLOBAL_SELECTOR)
  ? splitRule(styleString)
  : [`${className[0] === '@' ? '' : '.'}${className}{${styleString}}`]
).forEach((rule) => {
  sheet.insertRule(rule, sheet.cssRules.length)
})
```
Sources: [src/jsx/dom/css.ts:106-111](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L106-L111)

> [!WARNING]
> Pseudo-global selectors must use the exact `:-hono-global` identifier string defined by `PSEUDO_GLOBAL_SELECTOR` to trigger rule-splitting behavior during browser CSSOM synchronization.

Sources: [src/helper/css/common.ts:2](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/common.ts#L2), [src/jsx/dom/css.ts:106-111](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts#L106-L111)

## Related

- [JSX Runtime](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/jsx-runtime)
- [Client DOM and Hooks](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/client-dom-and-hooks)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
