# How Read Frog Manages Translation Node Styling: Presets and Custom CSS Integration

> Discover how Read Frog expertly handles translation node styling using eight presets and custom CSS. Learn about Zod schematization and runtime style injection for seamless UI integration.

- Repository: [MengXi/read-frog](https://github.com/mengxi-ream/read-frog)
- Tags: deep-dive
- Published: 2026-03-07

---

**Read Frog manages translation node styling through a dual-layer system that combines eight predefined visual presets with optional custom CSS, using Zod-schematized configuration and runtime style injection via `adoptedStyleSheets` or fallback style elements.**

Read Frog, an open-source browser extension for webpage translation, provides flexible translation node styling that balances ease of use with deep customization. The system defined in the `mengxi-ream/read-frog` repository allows users to select from preset visual themes or inject their own CSS rules, ensuring translated content integrates seamlessly with any website's design through configuration stored in [`src/types/config/translate.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/types/config/translate.ts) and runtime injection logic in [`src/utils/host/translate/ui/style-injector.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/ui/style-injector.ts).

## Translation Node Style Configuration Schema

The styling configuration is strictly typed using Zod schemas defined in **[`src/types/config/translate.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/types/config/translate.ts)**. This ensures type safety across the extension's options page and content scripts.

The core configuration object follows this structure:

```typescript
export const translationNodeStylePresetSchema = z.enum(TRANSLATION_NODE_STYLE)
export type TranslationNodeStylePreset = z.infer<typeof translationNodeStylePresetSchema>

export const translationNodeStyleConfigSchema = z.object({
  preset:   translationNodeStylePresetSchema,
  isCustom: z.boolean(),
  customCSS: z.string().max(MAX_CUSTOM_CSS_LENGTH).nullable(),
})

```

- **`preset`**: Stores one of the predefined constants from the `TRANSLATION_NODE_STYLE` array
- **`isCustom`**: Boolean toggle that switches between preset mode and custom CSS mode
- **`customCSS`**: User-provided stylesheet string with a maximum length of 8 KB (enforced by `MAX_CUSTOM_CSS_LENGTH`)

## Preset Style System and Constants

Read Frog ships with eight built-in visual presets defined in **[`src/utils/constants/translation-node-style.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/constants/translation-node-style.ts)**. These presets provide immediate visual differentiation for translated text without requiring CSS knowledge.

```typescript
export const DEFAULT_TRANSLATION_NODE_STYLE = "default"
export const TRANSLATION_NODE_STYLE_ON_INSTALLED = "textColor"
export const TRANSLATION_NODE_STYLE = [
  DEFAULT_TRANSLATION_NODE_STYLE,
  "blur",
  "blockquote",
  "weakened",
  "dashedLine",
  "border",
  "textColor",
  "background",
] as const

```

**Available preset styles include:**

- **default**: Standard formatting without special visual treatment
- **blur**: Applies a blur effect to indicate translated content
- **blockquote**: Styles translated text as a block quotation
- **weakened**: Reduces visual prominence of the translation
- **dashedLine**: Underlines translated content with a dashed border
- **border**: Surrounds translated nodes with a solid border
- **textColor**: Changes text color (used as the default for new installations)
- **background**: Applies a background color highlight

These constants populate the UI dropdown and drive the CSS classes injected at runtime.

## User Interface for Style Selection

The options page provides two distinct interfaces for managing translation node styling, both located in `src/entrypoints/options/pages/translation/custom-translation-style/`.

### Preset Style Selector

The **Preset Style Selector** component ([`preset-style-selector.tsx`](https://github.com/mengxi-ream/read-frog/blob/main/preset-style-selector.tsx)) renders a dropdown populated from the `TRANSLATION_NODE_STYLE` constant. When users select a preset, the component merges the selection into the global configuration atom:

```tsx
<Select
  value={translationNodeStyle.preset}
  onValueChange={(preset) => {
    if (!preset) return
    void setTranslateConfig(
      deepmerge(translateConfig, { translationNodeStyle: { preset } })
    )
  }}
>

```

### Custom CSS Editor

When users enable custom styling mode, the **CSS Editor** component ([`css-editor.tsx`](https://github.com/mengxi-ream/read-frog/blob/main/css-editor.tsx)) provides a text area for arbitrary CSS input. The component persists user input back to the configuration store:

```tsx
await setTranslateConfig(
  deepmerge(translateConfig, {
    translationNodeStyle: {
      ...translateConfig.translationNodeStyle,
      customCSS: cssInput,
    },
  })
)

```

This allows advanced users to override preset variables or define entirely new visual treatments.

## Runtime CSS Injection and Decoration

The core runtime logic resides in **[`src/utils/host/translate/ui/style-injector.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/ui/style-injector.ts)** and **[`src/utils/host/translate/ui/decorate-translation.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/ui/decorate-translation.ts)**. These utilities handle stylesheet injection into both standard Document contexts and Shadow DOM environments.

### Preset Style Injection

The `ensurePresetStyles` function bundles [`host-theme.css`](https://github.com/mengxi-ream/read-frog/blob/main/host-theme.css) (CSS variables) and [`custom-translation-node.css`](https://github.com/mengxi-ream/read-frog/blob/main/custom-translation-node.css) (preset class definitions) into a single string (`FULL_PRESET_CSS`). It injects styles using:

1. **`adoptedStyleSheets`** when the host environment supports Constructable Stylesheets
2. Fallback to a `<style>` element for older browsers

The function skips injection at the Document root because the extension manifest already injects preset CSS globally, but actively manages styles for Shadow DOM contexts.

### Custom CSS Injection

The `ensureCustomCSS` function in [`style-injector.ts`](https://github.com/mengxi-ream/read-frog/blob/main/style-injector.ts) first calls `ensurePresetStyles` to establish base variables, then injects user-provided CSS:

```typescript
export async function ensureCustomCSS(root: StyleRoot, cssText: string) {
  ensurePresetStyles(root)                // preset vars first
  // …adoptedStyleSheets or style element logic…
}

```

This function caches the last CSS string for each root to prevent redundant DOM operations.

### Node Decoration Logic

When translating a page, `decorateTranslationNode` determines which stylesheet to apply:

```typescript
export async function decorateTranslationNode(
  translatedNode: HTMLElement,
  styleConfig: TranslationNodeStyleConfig,
) {
  if (translationNodeStylePresetSchema.safeParse(styleConfig.preset).error) return

  const root = getContainingShadowRoot(translatedNode) ?? document

  if (styleConfig.isCustom && styleConfig.customCSS) {
    translatedNode.dataset[customTranslationNodeAttribute] = "custom"
    await ensureCustomCSS(root, styleConfig.customCSS)
    return
  }

  translatedNode.dataset[customTranslationNodeAttribute] = styleConfig.preset
  ensurePresetStyles(root)
}

```

The function sets a `data-read-frog-custom-translation-style` attribute on the translated node—either the preset name or `"custom"`—enabling targeted CSS selectors in both preset and custom stylesheets.

## Extending the Preset System

Developers can add new preset styles without modifying core injection logic:

1. **Add the preset constant** in [`src/utils/constants/translation-node-style.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/constants/translation-node-style.ts):

```typescript
export const TRANSLATION_NODE_STYLE = [
  /* existing values … */
  "underline",   // ← new preset
] as const

```

2. **Create CSS rules** in [`assets/styles/custom-translation-node.css`](https://github.com/mengxi-ream/read-frog/blob/main/assets/styles/custom-translation-node.css):

```css
[data-read-frog-custom-translation-style="underline"] {
  text-decoration: underline;
  text-decoration-color: var(--read-frog-primary);
}

```

The **Preset Style Selector** component automatically includes the new option because it reads directly from the `TRANSLATION_NODE_STYLE` constant.

## Summary

- **Read Frog** provides eight built-in translation node style presets (`default`, `blur`, `blockquote`, `weakened`, `dashedLine`, `border`, `textColor`, `background`) defined in [`src/utils/constants/translation-node-style.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/constants/translation-node-style.ts)
- The configuration schema in [`src/types/config/translate.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/types/config/translate.ts) enforces type safety using Zod, supporting both preset selection and custom CSS up to 8 KB
- **Runtime injection** via `ensurePresetStyles` and `ensureCustomCSS` in [`style-injector.ts`](https://github.com/mengxi-ream/read-frog/blob/main/style-injector.ts) uses Constructable Stylesheets (`adoptedStyleSheets`) with `<style>` element fallbacks for cross-browser compatibility
- **Node decoration** through `decorateTranslationNode` applies the `data-read-frog-custom-translation-style` attribute, enabling scoped CSS targeting in both standard pages and Shadow DOM contexts
- The system requires no code changes beyond CSS and constant additions to extend preset functionality

## Frequently Asked Questions

### What preset styles are available in Read Frog?

Read Frog includes eight preset styles defined in `TRANSLATION_NODE_STYLE`: `default`, `blur`, `blockquote`, `weakened`, `dashedLine`, `border`, `textColor`, and `background`. The `textColor` preset is applied by default when users first install the extension (`TRANSLATION_NODE_STYLE_ON_INSTALLED`), while `default` represents the fallback constant (`DEFAULT_TRANSLATION_NODE_STYLE`).

### How does Read Frog handle custom CSS injection?

Custom CSS is stored in the `translationNodeStyle.customCSS` configuration field and injected at runtime by the `ensureCustomCSS` function in [`src/utils/host/translate/ui/style-injector.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/host/translate/ui/style-injector.ts). The system first injects preset variables via `ensurePresetStyles`, then applies the user CSS using `adoptedStyleSheets` when available, falling back to a `<style>` element for broader compatibility. CSS is cached per root to avoid duplicate injections.

### Can I add my own preset styles to Read Frog?

Yes. To add a new preset, append your identifier to the `TRANSLATION_NODE_STYLE` array in [`src/utils/constants/translation-node-style.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/utils/constants/translation-node-style.ts), then add corresponding CSS rules to [`assets/styles/custom-translation-node.css`](https://github.com/mengxi-ream/read-frog/blob/main/assets/styles/custom-translation-node.css) targeting `[data-read-frog-custom-translation-style="your-preset-name"]`. The preset selector UI automatically populates from the constants array without requiring component modifications.

### How does the extension handle translation node styling in Shadow DOM contexts?

The `decorateTranslationNode` function detects Shadow DOM hosts using `getContainingShadowRoot(translatedNode)`. When a shadow boundary is detected, `ensurePresetStyles` and `ensureCustomCSS` inject stylesheets directly into the `ShadowRoot` rather than the global `document`, ensuring translated nodes within web components receive correct styling. The same `data-read-frog-custom-translation-style` attribute mechanism applies across both standard documents and shadow trees.