mirror of
https://github.com/resend/react-email.git
synced 2026-09-14 14:18:02 +08:00
26849a1b03
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
485 lines
11 KiB
Plaintext
485 lines
11 KiB
Plaintext
---
|
|
title: "Styling"
|
|
sidebarTitle: "Styling"
|
|
description: "Customize the editor's appearance with CSS variables, data attributes, and content classes."
|
|
icon: "paintbrush"
|
|
---
|
|
|
|
The editor's UI is fully customizable through CSS. Every component uses `data-re-*` attributes
|
|
as stable styling hooks, and a set of CSS custom properties (`--re-*`) control the entire color scheme.
|
|
|
|
## CSS variables
|
|
|
|
The default theme defines these CSS custom properties on `:root`. Override them on any parent
|
|
element to restyle the editor:
|
|
|
|
```css
|
|
.my-editor {
|
|
--re-bg: #1a1a2e;
|
|
--re-text: #eaeaea;
|
|
--re-border: #2a2a3e;
|
|
}
|
|
```
|
|
|
|
### Color variables
|
|
|
|
| Variable | Description | Light default | Dark default |
|
|
|----------|-------------|--------------|--------------|
|
|
| `--re-bg` | Background color | `#fff` | `#1c1c1c` |
|
|
| `--re-border` | Border color | `#e5e5e5` | `#2e2e2e` |
|
|
| `--re-text` | Primary text color | `#1c1c1c` | `#ececec` |
|
|
| `--re-text-muted` | Secondary/muted text | `#6b6b6b` | `#a0a0a0` |
|
|
| `--re-hover` | Hover background | `rgba(0,0,0,0.04)` | `rgba(255,255,255,0.06)` |
|
|
| `--re-active` | Active/click background | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.09)` |
|
|
| `--re-pressed` | Pressed/toggled background | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.09)` |
|
|
| `--re-separator` | Separator line color | `#e5e5e5` | `#2e2e2e` |
|
|
| `--re-danger` | Destructive action color | `#dc2626` | `#f87171` |
|
|
| `--re-danger-hover` | Destructive hover background | `rgba(220,38,38,0.1)` | `rgba(248,113,113,0.15)` |
|
|
|
|
### Dimension variables
|
|
|
|
| Variable | Description | Default |
|
|
|----------|-------------|---------|
|
|
| `--re-radius` | Border radius for containers (menus, dropdowns) | `0.75rem` |
|
|
| `--re-radius-sm` | Border radius for buttons and items | `0.5rem` |
|
|
| `--re-shadow` | Box shadow for floating elements | `0 10px 15px -3px rgba(0,0,0,0.1), ...` |
|
|
|
|
### Dark mode
|
|
|
|
The default theme supports dark mode in two ways:
|
|
|
|
**Automatic**: via `prefers-color-scheme`.
|
|
|
|
```css
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--re-bg: #1c1c1c;
|
|
--re-text: #ececec;
|
|
/* ... dark values applied automatically */
|
|
}
|
|
}
|
|
```
|
|
|
|
**Class-based**: via a `.dark` class on any ancestor.
|
|
|
|
```css
|
|
.dark {
|
|
--re-bg: #1c1c1c;
|
|
--re-text: #ececec;
|
|
/* ... */
|
|
}
|
|
```
|
|
|
|
This works out of the box with class-based dark mode libraries (e.g., `next-themes`).
|
|
|
|
## Data attribute selectors
|
|
|
|
Every UI component uses `data-re-*` attributes as stable CSS hooks. These are the recommended
|
|
way to target specific parts of the editor UI.
|
|
|
|
### Bubble menu
|
|
|
|
See the [Bubble Menu](/editor/features/bubble-menu) guide for setup, behavior, and composition.
|
|
|
|
```css
|
|
/* Menu container */
|
|
[data-re-bubble-menu] { }
|
|
|
|
/* Item groups (formatting, alignment) */
|
|
[data-re-bubble-menu-group] { }
|
|
|
|
/* Individual toggle buttons */
|
|
[data-re-bubble-menu-item] { }
|
|
|
|
/* Active/pressed state */
|
|
[data-re-bubble-menu-item][data-active] { }
|
|
|
|
/* Target a specific item by name */
|
|
[data-re-bubble-menu-item][data-item="bold"] { }
|
|
[data-re-bubble-menu-item][data-item="italic"] { }
|
|
|
|
/* Separator between groups */
|
|
[data-re-bubble-menu-separator] { }
|
|
```
|
|
|
|
Available `data-item` values: `bold`, `italic`, `underline`, `strike`, `code`, `uppercase`,
|
|
`align-left`, `align-center`, `align-right`.
|
|
|
|
### Node selector
|
|
|
|
The block type dropdown inside the bubble menu
|
|
|
|
```css
|
|
/* Dropdown wrapper */
|
|
[data-re-node-selector] { }
|
|
|
|
/* Trigger button (shows current block type) */
|
|
[data-re-node-selector-trigger] { }
|
|
|
|
/* Dropdown content panel */
|
|
[data-re-node-selector-content] { }
|
|
|
|
/* Individual block type option */
|
|
[data-re-node-selector-item] { }
|
|
|
|
/* Currently active block type */
|
|
[data-re-node-selector-item][data-active] { }
|
|
```
|
|
|
|
### Link selector
|
|
|
|
The link popover inside the bubble menu
|
|
|
|
```css
|
|
/* Wrapper */
|
|
[data-re-link-selector] { }
|
|
|
|
/* Trigger button */
|
|
[data-re-link-selector-trigger] { }
|
|
|
|
/* Edit form container */
|
|
[data-re-link-selector-form] { }
|
|
|
|
/* URL input field */
|
|
[data-re-link-selector-input] { }
|
|
|
|
/* Apply button */
|
|
[data-re-link-selector-apply] { }
|
|
|
|
/* Unlink/remove button */
|
|
[data-re-link-selector-unlink] { }
|
|
```
|
|
|
|
### Link bubble menu
|
|
|
|
The standalone menu that appears when clicking a link
|
|
|
|
```css
|
|
/* Container */
|
|
[data-re-link-bm] { }
|
|
|
|
/* Toolbar with action buttons */
|
|
[data-re-link-bm-toolbar] { }
|
|
|
|
/* Action buttons */
|
|
[data-re-link-bm-item] { }
|
|
[data-re-link-bm-item][data-item="edit-link"] { }
|
|
[data-re-link-bm-item][data-item="open-link"] { }
|
|
[data-re-link-bm-item][data-item="unlink"] { }
|
|
|
|
/* Edit form */
|
|
[data-re-link-bm-form] { }
|
|
[data-re-link-bm-input] { }
|
|
[data-re-link-bm-apply] { }
|
|
[data-re-link-bm-unlink] { }
|
|
```
|
|
|
|
### Button bubble menu
|
|
|
|
The menu that appears when clicking an email button
|
|
|
|
```css
|
|
/* Container */
|
|
[data-re-btn-bm] { }
|
|
|
|
/* Toolbar */
|
|
[data-re-btn-bm-toolbar] { }
|
|
|
|
/* Action buttons */
|
|
[data-re-btn-bm-item] { }
|
|
[data-re-btn-bm-item][data-item="edit-link"] { }
|
|
```
|
|
|
|
### Image bubble menu
|
|
|
|
The menu that appears when clicking an image
|
|
|
|
```css
|
|
/* Container */
|
|
[data-re-img-bm] { }
|
|
|
|
/* Toolbar with action buttons */
|
|
[data-re-img-bm-toolbar] { }
|
|
|
|
/* Action buttons */
|
|
[data-re-img-bm-item] { }
|
|
[data-re-img-bm-item][data-item="edit-link"] { }
|
|
[data-re-img-bm-item][data-item="unlink"] { }
|
|
|
|
/* Edit form */
|
|
[data-re-img-bm-form] { }
|
|
[data-re-img-bm-input] { }
|
|
[data-re-img-bm-apply] { }
|
|
[data-re-img-bm-unlink] { }
|
|
```
|
|
|
|
### Slash command
|
|
|
|
See the [Slash Commands](/editor/features/slash-commands) guide for setup, default items,
|
|
and custom commands.
|
|
|
|
```css
|
|
/* Command palette container */
|
|
[data-re-slash-command] { }
|
|
|
|
/* Scrollable viewport inside the container */
|
|
[data-re-slash-command-scroll] { }
|
|
|
|
/* Individual command item */
|
|
[data-re-slash-command-item] { }
|
|
|
|
/* Currently highlighted item */
|
|
[data-re-slash-command-item][data-selected] { }
|
|
|
|
/* Category header (e.g., "Text", "Layout") */
|
|
[data-re-slash-command-category] { }
|
|
|
|
/* Empty state ("No results") */
|
|
[data-re-slash-command-empty] { }
|
|
```
|
|
|
|
### Inspector
|
|
|
|
See the [Inspector](/editor/features/inspector) guide for setup, panels, and custom render props.
|
|
|
|
```css
|
|
/* Section container */
|
|
[data-re-inspector-section] { }
|
|
|
|
/* Section header with collapse toggle */
|
|
[data-re-inspector-section-header] { }
|
|
[data-re-inspector-section-toggle] { }
|
|
|
|
/* Section body (collapsible content) */
|
|
[data-re-inspector-section-body] { }
|
|
|
|
/* Field row (label + control) */
|
|
[data-re-inspector-field] { }
|
|
|
|
/* Text / number / textarea inputs */
|
|
[data-re-inspector-input] { }
|
|
[data-re-inspector-input][data-type="number"] { }
|
|
[data-re-inspector-input][data-type="textarea"] { }
|
|
|
|
/* Number field wrapper (input + unit label) */
|
|
[data-re-inspector-number] { }
|
|
[data-re-inspector-unit] { }
|
|
|
|
/* Select dropdown */
|
|
[data-re-inspector-select] { }
|
|
|
|
/* Color picker */
|
|
[data-re-inspector-color-control] { }
|
|
[data-re-inspector-color-trigger] { }
|
|
[data-re-inspector-color-hex] { }
|
|
|
|
/* Toggle group (e.g. alignment) */
|
|
[data-re-inspector-toggle-group] { }
|
|
[data-re-inspector-toggle-item] { }
|
|
[data-re-inspector-toggle-item][data-active] { }
|
|
|
|
/* Buttons */
|
|
[data-re-inspector-button] { }
|
|
[data-re-inspector-icon-button] { }
|
|
|
|
/* Breadcrumb navigation */
|
|
[data-re-inspector-breadcrumb-list] { }
|
|
[data-re-inspector-breadcrumb-item] { }
|
|
[data-re-inspector-breadcrumb-button] { }
|
|
[data-re-inspector-breadcrumb-separator] { }
|
|
|
|
/* Labels and text */
|
|
[data-re-inspector-label] { }
|
|
[data-re-inspector-prop-row] { }
|
|
```
|
|
|
|
## Editor content classes
|
|
|
|
The editor content area uses the `.tiptap` root class from [TipTap](https://tiptap.dev/docs/editor/getting-started/style-editor). Target content elements with `.tiptap` as a prefix.
|
|
|
|
### Typography
|
|
|
|
```css
|
|
/* All editor content */
|
|
.tiptap { }
|
|
|
|
/* Headings */
|
|
.tiptap h1 { }
|
|
.tiptap h2 { }
|
|
.tiptap h3 { }
|
|
|
|
/* Paragraphs */
|
|
.tiptap p { }
|
|
|
|
/* Block quotes */
|
|
.tiptap blockquote { }
|
|
|
|
/* Horizontal rules */
|
|
.tiptap hr { }
|
|
|
|
/* Code */
|
|
.tiptap code { } /* inline code */
|
|
.tiptap pre { } /* code block */
|
|
.tiptap pre code { } /* code inside block */
|
|
|
|
/* Lists */
|
|
.tiptap ul { }
|
|
.tiptap ol { }
|
|
```
|
|
|
|
### Layout and structure
|
|
|
|
```css
|
|
/* Column layouts */
|
|
.tiptap .node-columns { }
|
|
.tiptap .node-column { }
|
|
|
|
/* Sections */
|
|
.tiptap .node-section { }
|
|
|
|
/* Email buttons */
|
|
.tiptap .node-button { }
|
|
|
|
/* Links */
|
|
.tiptap .node-link { }
|
|
|
|
/* Placeholder text */
|
|
.tiptap .node-placeholder::before { }
|
|
```
|
|
|
|
### Text alignment
|
|
|
|
The alignment attribute renders as an HTML attribute on block nodes.
|
|
|
|
```css
|
|
.tiptap [alignment="left"] { text-align: left; }
|
|
.tiptap [alignment="center"] { text-align: center; }
|
|
.tiptap [alignment="right"] { text-align: right; }
|
|
```
|
|
|
|
### Column data types
|
|
|
|
Column layouts also use `data-type` attributes.
|
|
|
|
```css
|
|
.tiptap [data-type="two-columns"] { }
|
|
.tiptap [data-type="three-columns"] { }
|
|
.tiptap [data-type="four-columns"] { }
|
|
.tiptap [data-type="column"] { }
|
|
.tiptap [data-type="section"] { }
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Custom brand colors
|
|
|
|
Override the CSS variables to match your brand.
|
|
|
|
```css
|
|
.my-editor {
|
|
--re-bg: #fafaf9;
|
|
--re-border: #d6d3d1;
|
|
--re-text: #1c1917;
|
|
--re-text-muted: #78716c;
|
|
--re-hover: rgba(28, 25, 23, 0.04);
|
|
--re-active: rgba(28, 25, 23, 0.08);
|
|
--re-pressed: rgba(28, 25, 23, 0.08);
|
|
--re-radius: 0.5rem;
|
|
--re-radius-sm: 0.25rem;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<div className="my-editor">
|
|
<EditorProvider extensions={extensions} content={content}>
|
|
<BubbleMenu />
|
|
</EditorProvider>
|
|
</div>
|
|
```
|
|
|
|
### Custom bubble menu item styles
|
|
|
|
Increase icon size and change the active state color.
|
|
|
|
```css
|
|
[data-re-bubble-menu-item] svg {
|
|
width: 1rem;
|
|
height: 1rem;
|
|
}
|
|
|
|
[data-re-bubble-menu-item][data-active] {
|
|
background: #3b82f6;
|
|
color: #fff;
|
|
}
|
|
```
|
|
|
|
### Custom slash command appearance
|
|
|
|
Make the command palette wider with a custom background color.
|
|
|
|
```css
|
|
[data-re-slash-command] {
|
|
width: 320px;
|
|
max-height: 400px;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
[data-re-slash-command-item][data-selected] {
|
|
background: #e0f2fe;
|
|
}
|
|
|
|
[data-re-slash-command-category] {
|
|
color: #64748b;
|
|
font-size: 0.625rem;
|
|
}
|
|
```
|
|
|
|
### Styling editor content
|
|
|
|
Customize how content looks inside the editor.
|
|
|
|
```css
|
|
/* Custom heading styles */
|
|
.tiptap h1 {
|
|
font-size: 1.75rem;
|
|
letter-spacing: -0.025em;
|
|
color: #0f172a;
|
|
}
|
|
|
|
/* Custom blockquote */
|
|
.tiptap blockquote {
|
|
border-left: 4px solid #3b82f6;
|
|
background: #eff6ff;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0.25rem;
|
|
}
|
|
|
|
|
|
/* Custom button appearance in the editor */
|
|
.tiptap .node-button {
|
|
background: #2563eb;
|
|
color: #fff;
|
|
padding: 0.5rem 1.5rem;
|
|
border-radius: 0.375rem;
|
|
text-decoration: none;
|
|
}
|
|
```
|
|
|
|
<Tip>
|
|
Content styling in the editor (via `.tiptap` selectors) only affects how content looks while
|
|
editing. The exported email HTML is styled separately via the [Theming](/editor/features/theming) system.
|
|
</Tip>
|
|
|
|
## Examples
|
|
|
|
See styling and theming in action with runnable examples:
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Email Theming" icon="code" href="https://react.email/editor/examples/email-theming">
|
|
Basic/Minimal theme toggle with live preview.
|
|
</Card>
|
|
<Card title="Full Email Builder" icon="code" href="https://react.email/editor/examples/full-email-builder">
|
|
Complete editor with all styling features combined.
|
|
</Card>
|
|
</CardGroup>
|