Rich Text
The WYSIWYG interface stores its value as HTML. This page covers the Custom Formats option, which adds your own formatting entries to the editor, and what the stored HTML looks like so you can style it on your frontend.
Custom Formats
Custom Formats takes a JSON array of formatting entries. Set it on the field's interface options. When the array is not empty, a Formats dropdown is added to the editor toolbar. There is no toolbar option to enable it.
The format is a subset of TinyMCE's style_formats, so existing configurations often carry over unchanged.
Every entry needs a title and one of the following keys:
| Key | Applies to |
|---|---|
inline | A tag wrapped around the selected text, such as span. |
block | A single block tag. Converts the block when its tag differs. See Block formats. |
selector | One or more comma-separated block tags. Never converts the block. |
items | An array of entries, grouped into a submenu. See Grouped formats. |
Each entry also takes optional classes, styles, and attributes.
Every entry needs classes or attributes as well. The editor uses them to recognize the format again when the value is reloaded, so an entry without either is skipped.
Example
[
{ "title": "Lead paragraph", "block": "p", "classes": "lead" },
{ "title": "Subheading", "selector": "h2,h3", "classes": "subheading" },
{
"title": "Callouts",
"items": [
{ "title": "Info", "block": "p", "classes": "callout callout-info" },
{ "title": "Warning", "block": "p", "classes": "callout callout-warning" }
]
},
{
"title": "Highlight",
"inline": "span",
"classes": "highlight",
"styles": { "background": "#ffff00" }
}
]
Applying Lead paragraph and Highlight produces:
<p class="lead">A <span class="highlight" style="background: #ffff00">lead</span> paragraph.</p>
Define lead, subheading, callout, and highlight in your frontend's CSS. Directus stores the classes but does not style them. See Styling the output on your frontend.
Inline formats
An inline entry renders a tag around the selection, carrying its classes, its attributes, and its styles serialized into a style attribute:
{
"title": "Highlight",
"inline": "span",
"classes": "highlight",
"styles": { "background": "#ffff00" },
"attributes": { "title": "Highlighted text" }
}
<span class="highlight" style="background: #ffff00" title="Highlighted text">text</span>
Inline entries are the only formats that write a style attribute into the content, so they are also the only ones with a visible effect inside the editor. The trade-off is an inline style in your stored HTML, which is harder to override from your frontend CSS.
Block formats
A block entry applies to one block tag and takes a single tag only. Use selector for a list.
{ "title": "Lead paragraph", "block": "p", "classes": "lead" }
When the selected block's tag differs from the entry's tag, the block is converted. Conversion works between paragraphs and headings only, because those are the two block types that hold the same content. A block: 'h2' entry turns a paragraph into <h2 class="…">, and a block: 'p' entry turns a heading back into a paragraph.
Every other tag applies without converting. A block: 'blockquote' entry formats blocks that are already <blockquote> and leaves everything else alone, and logs a warning in the browser console. Use selector when you never want conversion.
Selector formats
A selector entry applies to the block tags you list and never changes the block's type:
{ "title": "Subheading", "selector": "h2,h3", "classes": "subheading" }
Applying this to an <h2> or <h3> adds the class. Applying it to a paragraph does nothing.
Grouped formats
An items array groups entries into a submenu in the Formats dropdown:
{
"title": "Callouts",
"items": [
{ "title": "Info", "block": "p", "classes": "callout callout-info" },
{ "title": "Warning", "block": "p", "classes": "callout callout-warning" }
]
}
Groups support one level. A group nested inside another group is skipped, and a group whose entries are all invalid is dropped along with them.
Which tags a block format can target
block and selector accept only tags the editor models as a node:
p, h1 through h6, pre, blockquote, section, article, figure, figcaption, details, summary, dl, dt, dd, hr, img, ul, ol, li, table, tr, td, th, video, audio, iframe.
div and span are not on the list, so they cannot carry a block format. A block: 'div' entry is skipped. This matters when you migrate a TinyMCE configuration, since TinyMCE formats commonly wrap content in a div. It is the same reason a stored <div class="float-left"> is unwrapped when the editor loads it. Use a section, article, or figure entry instead, or move the wrapper into your frontend template.
For a selector entry, one unsupported tag skips the whole entry. Split the tags across separate entries if you want the supported ones to keep working.
Invalid entries are skipped
An entry the editor cannot build is skipped, and the rest of the array still loads. Every skipped entry logs a warning to the browser console. Nothing appears in the interface, so check the console when a format does not show up in the dropdown.
Entries are skipped when they:
- Have no
title. - Have none of
inline,block,selector, oritems. - Have no
classesand noattributes. - Use
wrapper, which is not supported. - Name a tag the editor does not model, such as
div. - Pass a comma-separated list to
blockinstead ofselector. - Nest a group inside another group.
Gotchas
The editor does not load your frontend CSS
Directus loads no project stylesheet into the editing surface, so a class like lead, float-left, or text-center has no visual effect inside the WYSIWYG. The class is stored and round-trips correctly. It just has nothing styling it in the Data Studio.
This surprises authors, who apply a format, see no change, and assume it is broken. Point them at the Formats dropdown instead: it shows the active format in its label and highlights the matching row. That is the confirmation that a format applied, not the rendered content.
The editor's own content styles can also contradict your class. The editor styles figure as display: table; margin: 0.8125rem auto, so a captioned image with a float-left class renders centered in the editor and floated on your frontend. The same applies to anything depending on display, float, width, or a flex or grid context your frontend provides.
There is currently no option to inject custom CSS into the editor. The deprecated Options Override (tinymceOverrides) option is no longer applied, so TinyMCE's content_css has no equivalent.
Because of this, layout classes are the riskiest thing to hand to non-technical authors: no feedback in the editor, and a result that depends entirely on CSS the editor cannot see. Prefer semantic formats such as lead, callout, or subheading over layout ones where you can.
styles on a block entry only styles the dropdown
On an inline entry, styles is written into the content as a style attribute. On a block or selector entry, styles only styles the entry's label inside the Formats dropdown. It is not written to the content.
The editor does not round-trip style on block nodes. Only class, id, title, role, lang, dir, data-*, and aria-* survive a save and load cycle. TinyMCE applied block styles to the block itself, so this is a behavior change to account for when migrating.
Use classes for block styling and define the CSS on your frontend.
Attributes on a block entry are filtered
attributes on a block or selector entry accepts only id, title, role, lang, dir, data-*, and aria-*. Anything else, including style and target, is dropped with a console warning.
attributes: { "class": "…" } is merged into classes, so toggling the format off removes those classes too.
Inline entries do not filter attributes.
Toggling behavior
Selecting an active format in the dropdown removes it. Applying and removing a block format behaves as follows:
- Removing a format strips only the classes and attributes that format configured. Unrelated classes,
id,data-*, andaria-*on the same block survive. - The block keeps its tag. A
block: 'h2'format does not turn the heading back into a paragraph when you remove it. - A format applies to every eligible block in the selection as a single undo step.
- A format counts as active when every eligible block in the selection carries all of its classes, or all of its attributes for a format anchored on attributes alone.
Image captions
Adding or editing an image opens a drawer with a Caption field. A non-empty caption wraps the image in a <figure>:
<figure>
<img src="https://example.com/assets/2b1a…" alt="A wind turbine" />
<figcaption>A wind turbine at dusk.</figcaption>
</figure>
Style figure and figcaption on your frontend to match. The editor centers them, which your own CSS will override.
Behavior worth knowing:
- Clearing the caption reverts to a bare
<img>, but only when the<figure>holds nothing but images and carries no attributes of its own. A<figure class="…">you added deliberately is kept, and its caption is removed on its own. - Editing an existing image updates it in place, so its attributes and its surrounding
<figure>survive the edit. - Pressing
Enterinside a caption leaves the figure and starts a new paragraph after it, rather than adding a second<figcaption>. - Pressing
Backspacein an empty caption removes the caption. - Deleting the image out of a figure removes the orphaned caption too.
- Stored
<figure>and<figcaption>markup round-trips, including a caption placed before the image and a figure holding only a caption.
Styling the output on your frontend
Directus stores the HTML. Rendering and styling it is your frontend's job.
Add the classes your formats configure to your stylesheet:
.lead {
font-size: 1.25rem;
line-height: 1.6;
}
.subheading {
color: #6644ff;
text-transform: uppercase;
}
.callout {
border-left: 4px solid;
padding: 1rem;
}
.callout-info { border-color: #3399ff; }
.callout-warning { border-color: #ffaa00; }
Two things to keep in mind:
- Scope these styles so they do not collide with the rest of your site. Rendering the value inside a wrapper such as
.proseand prefixing your selectors keeps author-applied classes from leaking. - Review your selectors before upgrading to Directus 12 if you style stored HTML by tag, class, or attribute. Markup the editor does not model, such as
<div>wrappers, is removed when a field is edited and saved.
Next Steps
Read about the WYSIWYG interface options, the supported HTML and normalization behavior, and keyboard shortcuts for the editor.
Get once-a-month release notes & real‑world code tips...no fluff. 🐰
Interfaces
Manage your data effectively with Directus fields. Discover various field types, interfaces, validations, and relationships to perfectly suit your data modeling needs.
Relationships
Leverage Directus relationships to create powerful data connections. Explore Many to One, One to Many, Many to Many, and Many to Any relationships, along with Translations for multilingual content management.