Toolbar
Qalma provides a small structural toolbar primitive and a command directive. It does not provide toolbar layouts, icons, menus, or visual variants. Your app chooses those pieces.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
HeadingsPlugin,
HistoryPlugin,
QalmaCommand,
QalmaContent,
QalmaEditor,
QalmaToolbar,
TextFormattingKit,
createQalmaEditor,
} from '@qalma/editor';
@Component({
selector: 'app-basic-toolbar',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [QalmaEditor, QalmaContent, QalmaToolbar, QalmaCommand],
template: `
<qalma-editor [editor]="editor">
<qalma-toolbar label="Formatting">
<button type="button" qalmaCommand="setParagraph">P</button>
<button type="button" qalmaCommand="toggleHeading2">H2</button>
<button type="button" qalmaCommand="toggleBold">B</button>
<button type="button" qalmaCommand="toggleItalic">I</button>
<button type="button" qalmaCommand="undo">Undo</button>
<button type="button" qalmaCommand="redo">Redo</button>
</qalma-toolbar>
<qalma-content class="block min-h-48 p-4 [&_.ProseMirror]:outline-none" />
</qalma-editor>
`,
})
export class BasicToolbar {
protected readonly editor = createQalmaEditor({
plugins: [HeadingsPlugin, ...TextFormattingKit, HistoryPlugin],
});
}
QalmaToolbar
<qalma-toolbar> projects your controls and adds:
| Attribute | Value |
|---|---|
| CSS class | qalma-toolbar |
role |
toolbar |
aria-label |
label() input, defaulting to Editor toolbar |
Use the label input when a page has more than one toolbar or when a more
specific label helps screen-reader users.
<qalma-toolbar label="Block formatting">
<button type="button" qalmaCommand="toggleBulletList">Bullet list</button>
</qalma-toolbar>
QalmaCommand
button[qalmaCommand] must be inside <qalma-editor> so it can read the
controller from context.
<button
type="button"
qalmaCommand="setTextColor"
[qalmaCommandValue]="'rgb(67, 56, 202)'"
>
Indigo
</button>
The directive handles selection preservation, disabled state, active class, and
aria-pressed for commands that expose active state. Style
.qalma-command-active from your app.
.toolbar button.qalma-command-active {
border-color: var(--accent);
background: var(--accent-subtle);
}
Custom controls
Not every control should use qalmaCommand. Inputs, selects, upload buttons,
popover triggers, and color swatches often need command values or extra app
state. Use the controller directly for those.
readonly codeBlockLanguage = computed(
() => this.editor.query<string>('codeBlockLanguage') ?? 'plaintext',
);
setCodeBlockLanguage(event: Event): void {
const target = event.target;
if (target instanceof HTMLSelectElement) {
this.editor.execute('setCodeBlockLanguage', target.value);
}
}
<select
[value]="codeBlockLanguage()"
(change)="setCodeBlockLanguage($event)"
aria-label="Code block language"
>
<option value="plaintext">Plain text</option>
<option value="typescript">TypeScript</option>
</select>
Complex toolbar UI
The docs playground toolbar is intentionally app code. It uses icons, Tailwind classes, color swatches, an image upload input, a link popover trigger, and a language select. Those controls call the same public API:
protected setTextColor(color: string): void {
this.editor().execute('setTextColor', color);
}
protected canSetTextColor(color: string): boolean {
return this.editor().canExecute('setTextColor', color);
}
protected isTextColorActive(color: string): boolean {
return this.editor().query<string>('textColor') === color;
}
When you use a component system such as Spartan/helm or another shadcn-style set of primitives, keep it in the application layer. Qalma's library primitives stay unstyled so the same editor can fit any design system.
For selection-based toolbars, add SelectionPlugin, listen for
qalma-selection-update, and render your app overlay from
query. The docs playground contextual toolbar is
one consumer-owned example of that pattern.