This guide is the tour of what the editor does, and how to choose it for each field: the built-in features, their toolbar buttons, keyboard and Markdown shortcuts, colors, links, code blocks, tables, and the toolbar itself. To see them all at once, run the development server of this repository (mix dev, see the README).

The features

Each built-in feature is a part of the editor that a field can have or not (Kotoba.Features):

FeatureWhat it isToolbarKeyboardMarkdown
boldbold textBoldCmd/Ctrl+B**text**, __text__
italicitalic textItalicCmd/Ctrl+I*text*, _text_
underlineunderlined textUnderlineCmd/Ctrl+U
strikethroughstruck textStrikethrough~~text~~
highlighthighlights and text colorsHighlight (the palette)Cmd/Ctrl+Shift+H==text==
subscriptsubscript text(an app's toolbar)Cmd/Ctrl+,
superscriptsuperscript text(an app's toolbar)Cmd/Ctrl+.
inline_codecode in a lineInline code`code`
linkslinks and autolinksLinkCmd/Ctrl+K[text](url)
headingsheadings 1 to 4Heading 1 to 4# to ####
quotesblock quotesQuote>
listsbulleted and numbered listsBulleted list, Numbered listTab, Shift+Tab-, *, +, 1.
check_listscheck lists (needs lists)Check listSpace on a focused box[ ], [x], - [ ]
code_blockscode blocks, highlighted, with a language pickerCode block, Code languageTab inserts a tab```, ```elixir
horizontal_ruleshorizontal rulesHorizontal rule---, ***, ___
tablestablesTable, and the Table groupTab, Shift+Tab
attachmentsfiles, images and galleries (with uploads)Attach a file, and the Image groupdrop, paste; Alt+Left, Alt+Right in a gallery; Alt+F10 to the caption
mentionsprompts and mentions (with prompts)the trigger
imagesimages from a URL, in the text![alt](url) (pasted)
footnotesfootnotes and their references[^1] and [^1]: text (pasted)

Pasted Markdown is not a feature: plain text that reads as Markdown (a README, a note from a Markdown app) comes in as rich text, read on the server as Kotoba.Content.from_markdown/2 reads it, with the features of the editor. <.kotoba> renders the server's half, so the app adds no code; paste_markdown={false} turns it off. Images and footnotes come from pasted Markdown and from the server; the editor has no button for them yet.

Suggestions are not a feature: an editor with assist has an Assist button, and any editor shows the text that the server streams with Kotoba.Live.stream_start/4 (see Suggestions). A suggestion has only the editor's features.

Cmd is the key on a Mac, Ctrl elsewhere. A Markdown shortcut acts as you type it: ## at the start of a line makes a heading, and **bold** becomes bold when you type the last *. ***text*** makes bold and italic text.

Paragraphs, line breaks (Shift+Enter), tabs, undo (Cmd/Ctrl+Z) and redo (Cmd/Ctrl+Shift+Z) are always there. The case formats (lowercase, uppercase, capitalize) are not features: the editor keeps them when they come in a pasted text or a stored document.

Choose the features of a field

With no features, an editor has every feature. Give a list for fewer:

<.kotoba field={@form[:comment]} id="comment" features={~w(bold italic links lists mentions)} />
<.kotoba field={@form[:body]} id="body" />

A feature that is off is not in the editor at all:

  • its buttons are not in the default toolbar, and are hidden in a custom one;
  • its Markdown shortcut types the text, and its keyboard shortcut does nothing;
  • pasted content of it comes in as plain paragraphs and text: a pasted table, heading or code block is paragraphs, and a pasted format is dropped;
  • a stored node of it loads as an unknown node: the editor shows its type in a placeholder, and saves it with no change.

attachments needs uploads too (see the Uploads guide), and mentions needs prompts (see Prompts and mentions).

Check them on the server

The editor keeps out what it does not have, but a request can post any document. Check the field in its changeset:

def changeset(comment, attrs) do
  comment
  |> cast(attrs, [:body])
  |> Kotoba.Content.validate_features(:body, ~w(bold italic links lists mentions)a)
end

A document with another feature gets the error "has content that is not allowed: tables" (with validation: :features and the names in the error's keys). Kotoba.Features.used/1 and Kotoba.Features.check/2 read the features of a Kotoba.Document. App nodes are not features: the check allows them.

Text formats and colors

The Text group of the default toolbar has Bold, Italic, Underline, Strikethrough, Highlight and Inline code. A format button has aria-pressed, and the live region says "Bold on" or "Bold off".

Subscript and superscript are features with their keyboard shortcuts, but have no button in the default toolbar. An app's toolbar can have them (command="subscript", command="superscript", see below). Turning one on turns the other off.

The Highlight button opens the color palette: seven text colors, seven highlights (red, orange, yellow, green, blue, purple and gray) and "Remove color". Cmd/Ctrl+Shift+H and ==text== give the default highlight, yellow. Text colors are part of the highlight feature.

The document stores a color as its name, never as a CSS value, and the HTML has classes: span.kotoba-color-red and mark.kotoba-highlight-green (the default highlight is a plain mark). kotoba.css gives each name a color that reads on light and dark backgrounds; the Theming guide shows how to set your own. Kotoba.Nodes.Text.colors/1 reads the colors of a text node, and Kotoba.Nodes.Text.palette/0 gives the names.

The Link button or Cmd/Ctrl+K opens a small form for the URL of the selected text. Enter applies it, an empty URL removes the link, and Escape closes the form. Pasting a URL over selected text makes a link, and the editor links the URLs and email addresses that you type (autolinks).

A link can be relative (/docs, #part) or have an allowed scheme: http, https and mailto by default. To allow others:

config :kotoba, allowed_link_schemes: ~w(http https mailto tel)

A link with another scheme becomes its text, in the editor and in the HTML. The HTML of a link has rel="noopener nofollow".

Lists and check lists

Tab indents a list item and Shift+Tab outdents it (in an item that is not indented, Shift+Tab leaves the editor). In a check list, a click on the box, or Space when the box has the focus, checks the item. The HTML of a check list is ul.kotoba-check, with li.kotoba-checked and li.kotoba-unchecked items, which kotoba.css draws with a box.

Code blocks

In a code block, the toolbar shows the code language picker. It offers plain text and the 28 languages of Kotoba.CodeLanguages, and the editor highlights the code as you type. ```elixir at the start of a line makes a code block in that language. To offer fewer languages, give their ids or aliases:

<.kotoba field={@form[:body]} id="post-body" code_languages={~w(elixir erlang sql bash)} />

A name that is not a language, and an empty list, raise ArgumentError. The list chooses what the picker offers, not what the editor keeps: a pasted or stored block in another language keeps its language, is highlighted when the editor knows it, and shows in the picker.

The HTML of a code block is pre with code.language-<language>, and no highlighting. To highlight rendered code, use a highlighter of your own on the page, for example Prism with a Prism theme: the editor's own copy of Prism does not touch the page's window.Prism (see the Theming guide).

Galleries

Images uploaded together make a gallery, a grid of images. Select an image (a click, or the arrow keys from the text next to it) for the Image group of the toolbar: Gallery groups the image with the images next to it, or ungroups its gallery, and Move image left and Move image right (or Alt+Left and Alt+Right) reorder it. See the Uploads guide.

Tables

The Table button inserts a table of three rows and three columns, with a header row. In a table, the Table group of the toolbar has Insert row above, Insert row below, Insert column before, Insert column after, Header row, Header column, Delete row, Delete column and Delete table. Tab and Shift+Tab move between the cells; Alt+F10 goes to the toolbar.

A pasted HTML table becomes a table. The editor has no command to merge cells, but it keeps the merged cells of a pasted or stored table (colspan, rowspan).

The HTML is a table.kotoba-table in a div.kotoba-table-scroll (which scrolls a wide table sideways), with a thead for a header row and th scope="col" or th scope="row" for header cells. kotoba.css styles it. The Markdown of a table is a GitHub Flavored Markdown table, with the limits in the Known limits guide.

The toolbar

The default toolbar has the buttons of the editor's features, in groups: Text (formats and Link), Blocks (headings and Quote), Lists, Insert (Code block, Horizontal rule, Table, Attach a file), Assist (with assist), the extensions' buttons, and History (Undo, Redo). The Code group (the language picker) shows in a code block, the Table group in a table, and the Image group (Gallery, Move image left, Move image right) when an image is selected. The toolbar is one tab stop, with the arrow keys between the buttons (see the Accessibility guide).

A custom toolbar

To choose the buttons, their order or their icons, give a toolbar slot with Kotoba.Components.kotoba_toolbar/1:

<.kotoba field={@form[:body]} id="post-body">
  <:toolbar>
    <.kotoba_toolbar>
      <:button command="bold"><.icon name="hero-bold" /></:button>
      <:button command="italic"><.icon name="hero-italic" /></:button>
      <:button command="link" label="Add a link"><.icon name="hero-link" /></:button>
      <:button command="subscript" label="Subscript" class="my-button">x₂</:button>
      <:button command="undo"><.icon name="hero-arrow-uturn-left" /></:button>
    </.kotoba_toolbar>
  </:toolbar>
</.kotoba>

Each button slot has a command, an optional label (the accessible name and the title; the command's own label by default) and an optional class. With no slots, commands gives text buttons for a list of commands:

<.kotoba_toolbar commands={~w(bold italic underline highlight subscript superscript undo redo)} />

Kotoba.Components.toolbar_commands/0 lists the commands: bold, italic, underline, strikethrough, highlight, code, subscript, superscript, link, h1 to h4, quote, bullet, number, check, code-block, rule, table, upload, assist, code-language, the table-* commands, gallery, image-previous, image-next, undo and redo. An extension's control is <extension>:<command> (see the Extensions guide). Another command raises ArgumentError.

  • The editor adds role="toolbar", the roving tabindex, aria-pressed and aria-disabled; label names the toolbar ("Formatting" by default), and class adds classes.
  • The buttons of a feature that the editor does not have are hidden.
  • The table-* buttons are hidden when the selection is not in a table, and gallery, image-previous and image-next when no image is selected.
  • code-language renders a <select>, the code language picker: the editor fills it, and shows it in a code block.
  • highlight opens the color palette.
  • assist opens the Assist menu, and is hidden in an editor with no actions.

A toolbar can also be outside the editor, anywhere on the page, with for set to the editor's id:

<.kotoba_toolbar for="post-body" commands={~w(bold italic link)} />
<.kotoba field={@form[:body]} id="post-body" />

The selection menu

A menu can float over the selected text, as in Notion, Medium or Tiptap's bubble menu. Turn it on with selection_menu:

<.kotoba field={@form[:body]} id="post-body" selection_menu />

The default menu has Bold, Italic, Underline, Strikethrough, Inline code, Highlight and Link. A list chooses the commands, in order; they are the toolbar's commands, and an extension's control is <extension>:<command>:

<.kotoba field={@form[:body]} selection_menu={~w(bold italic link h2 quote callout:toggle)} />

The menu shows above a selection of text, or below it when there is no room above (in the editor or in the window). It waits for the mouse button to come up, so it does not follow a drag. It does not show for a caret, in a code block, in a read-only editor, or while the link form, the palette or a prompt menu is open. Its buttons work as the toolbar's buttons: aria-pressed follows the selection, and the buttons of a feature that the editor does not have are left out.

  • Alt+F10 moves the focus to the menu (to the toolbar when no menu shows); the arrow keys move between its buttons.
  • Escape closes it until the selection changes.

An app's own menu

For the app's own icons, labels or classes, give a selection slot with Kotoba.Components.kotoba_selection_menu/1. It takes button slots, as kotoba_toolbar/1 does:

<.kotoba field={@form[:body]} id="post-body">
  <:selection>
    <.kotoba_selection_menu class="my-menu">
      <:button command="bold"><.icon name="hero-bold" class="size-4" /></:button>
      <:button command="italic"><.icon name="hero-italic" class="size-4" /></:button>
      <:button command="link" label="Add a link"><.icon name="hero-link" class="size-4" /></:button>
    </.kotoba_selection_menu>
  </:selection>
</.kotoba>

Style the menu

The menu is a div.kotoba-selection-menu with data-placement="top" or "bottom"; its buttons are .kotoba-toolbar-button, in .kotoba-toolbar-group elements. These custom properties theme it and the link card, and each one falls back to the editor's own property:

PropertyDefault
--kotoba-floating-background--kotoba-background
--kotoba-floating-text--kotoba-text
--kotoba-floating-border--kotoba-border
--kotoba-floating-hover--kotoba-surface
--kotoba-floating-shadow--kotoba-shadow
--kotoba-floating-radius--kotoba-radius

For example, a dark menu over a light editor:

.kotoba-selection-menu,
.kotoba-link-card {
  --kotoba-floating-background: #1f2023;
  --kotoba-floating-text: #f4f4f5;
  --kotoba-floating-border: #34353a;
  --kotoba-floating-hover: #34353a;
  --kotoba-floating-radius: 10px;
}

.kotoba-selection-menu .kotoba-toolbar-button[aria-pressed="true"] {
  background: #e8590c;
  color: white;
}

The menu shows with a short animation, which is off with prefers-reduced-motion.

When the caret is in a link, a card shows under it: the URL, which opens in a new tab, and Edit link (the link form) and Remove link buttons. An automatic link (a URL typed in the text) has only the URL. Alt+F10 moves the focus to the card, and Escape closes it until the caret moves. Cmd/Ctrl+click on a link in the editor opens it in a new tab. The card is a div.kotoba-link-card, themed with the same properties as the selection menu.

The caption field

When an attachment is selected, a field shows under it for its caption (see Captions). Alt+F10 moves the focus to it, Enter keeps the caption, and Escape in the editor hides it until the selection changes. It is a div.kotoba-caption-field with an input.kotoba-caption-input, themed with the same properties as the selection menu.

More