Callouts
@nextgensoftwares/folio-plugin-callout adds callout (admonition) blocks: tinted, rounded boxes with an icon and an optional title, holding any blocks. Users create and edit them entirely from the UI. It's also the finished version of the custom block tutorial.
import { calloutPlugin } from '@nextgensoftwares/folio-plugin-callout';
const composed = composePlugins(standardSchema, [calloutPlugin()]);Try it
createFolioApp({ doc, measurer, plugins: [calloutPlugin()] }) in a <FolioApp>. Put the caret in a callout to see its bar under the toolbar, or type /callout on an empty line.
In the editor
| To… | Do |
|---|---|
| insert | type /callout (or /warning, /tip, /info…), or toolbar Insert ▸ Callout ▸ Info/Note/Tip… |
| wrap existing blocks | select them and press Ctrl/⌘+Alt+K, or right-click ▸ Wrap in callout |
| unwrap | Ctrl/⌘+Alt+K inside the callout, context bar ▸ Unwrap, or Backspace at the start of its first line |
| leave it | Enter on an empty last line (the line moves below the callout) |
| style it | with the caret inside, the context bar under the toolbar: variant swatches, Soft / Outline / Accent bar, icon picker, title toggle and text, fill / border / accent colours, collapsible, remove |
The right-click menu has the same entries under Callout. Enter on any other empty line inside a callout adds a line: a callout is never cut in two.
Restyling never wraps
Every style command (variant, style, icon, title, colours, collapsible) targets the selection's callout: the innermost callout holding the whole selection, whether it's a caret, a word, a whole paragraph or several paragraphs, or for a range crossing a callout's edge, the callout at its start (then its end). They only change that node's attributes.
No callouts inside callouts
By default a callout never goes inside another one, at any depth:
- Insert ▸ Callout ▸ Tip or /tip inside a callout restyles that callout (it becomes a Tip) instead of adding a box in a box;
- Wrap refuses a selection inside a callout or one containing a callout;
- Ctrl/⌘+Alt+K inside a callout (caret or range) unwraps it.
calloutPlugin({ allowNested: true }) allows nesting through the explicit insert/wrap commands. The rule lives in the commands, not in the content expression: Folio content expressions can't say "any block but a callout", and a schema-level ban would make existing documents that already nest invalid. Those documents still load and render (nested boxes lay out fine). Right-click ▸ Merge nested callouts (flattenNestedCallouts()) repairs them in one undo step, unwrapping every inner callout into its parent and keeping its title as a bold paragraph. Pasting a callout into a callout can still nest one; the same command fixes it.
The node
{ type: 'callout', attrs: { variant: 'warning', title: 'Before you start', appearance: 'accent' }, content: [/* blocks */] }| Attribute | Values | Default |
|---|---|---|
variant | info note tip success warning danger quote custom | info |
appearance | soft (tint + thin border), outline (border only), accent (tint + start-side bar) | the variant's (quote: accent) |
icon | a name from the safe set, 'none', or null for the variant's icon | null |
title | text (≤ 200 chars) shown bold next to the icon, or null | null |
fill, border, accent | CSS colours overriding the variant's (accent also colours the icon, bar and title) | null |
collapsible | read-mode hint, see below | false |
dir | ltr / rtl, or null to follow the first block | null |
tone | legacy alias of variant (older documents); wins while set, cleared by the variant picker | null |
Every attribute is validated: validate() reports bad values, and the renderer, painters and exporters fall back to defaults for anything pasted or loaded that doesn't pass. Colours accept hex, rgb()/rgba(), hsl()/hsla() and common colour names only, so nothing else ever reaches a style attribute, a PDF operator or DOCX XML.
The title is an attribute rather than an editable first paragraph: content stays plain block+, unwrapping is lossless (the title becomes a bold paragraph), and the title can't be accidentally merged into the content.
collapsible
Folio lays out one way for the screen, PDF and print, so a collapsible callout always lays out (and prints) in full. The attribute is a hint for read-mode viewers that render the document differently (an HTML reader, a protected viewer) and may show it collapsed to its title there. Exports always contain the whole content.
Pagination
The box splits cleanly across pages. Its straight middle (fill, side borders, accent bar) is made of rect fragments, which pagination clips at page edges; the rounded ends are calloutCap custom fragments, which land only on the pages holding the true top and bottom. A callout across a page break has rounded corners only at its real ends and open edges at the cut, on screen and in the PDF.
Breaks come only from the content (widows/orphans and keep rules inside it apply). The title and icon never end a page on their own: the first possible break is after the first lines of content.
Colours, dark mode and themes
Every colour is drawn through the colour roles of the active colour theme: fill as fill, border and accent bar as border, the icon as text on the adapted fill, the title through the text painter. Dark mode and custom document themes adapt them.
Each variant also has a dark colour set (deep fills, bright accents) for documents whose own theme is dark: palette: 'auto' (default) picks it when the layout theme's body text is light. Force one with palette: 'light' or 'dark'.
Export
- PDF draws from the layout: rects natively, the caps (and the icon, drawn by the top cap) through the plugin's
exporters.pdfpainter, as vector paths with the same icon geometry as the screen. - DOCX writes a one-cell table: Word's closest box that holds any blocks and keeps shading and borders when edited. Soft = shading + thin border, outline = border only, accent = shading + a thick start-side border (right in RTL). The title is a bold first paragraph (kept with the content), preceded by a symbol for the icon.
Options
calloutPlugin({
allowNested: false, // callouts inside callouts via insert/wrap
palette: 'auto', // variant colour set
metrics: { padX: 16 }, // box geometry, px
wrapKey: 'Mod-Alt-k', // or false
toolbar: true, // Insert ▸ Callout
slash: true, // /callout …
});Context-bar items have ids callout.variant, callout.appearance, callout.icon, callout.title, callout.colors, callout.collapsible, callout.unwrap, callout.remove (group callout), so hosts can route them elsewhere with ui.route (e.g. { 'callout.variant': ['bubble', 'contextBar'] }) or hide some. API: @nextgensoftwares/folio-plugin-callout.