<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400..600;1,9..144,400..600&family=Imperial+Script&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" />

Search the Qalma docs

Type at least two characters to get started.

Docs

Recipes

These recipes use only the public @qalma/editor API. They are small on purpose; compose them into your own Angular components and design system.

Minimal editor

const editor = createQalmaEditor({
  content: '<p>Hello world</p>',
  plugins: [...TextFormattingKit, HistoryPlugin],
});
<qalma-editor [editor]="editor">
  <qalma-toolbar>
    <button type="button" qalmaCommand="toggleBold">Bold</button>
    <button type="button" qalmaCommand="undo">Undo</button>
    <button type="button" qalmaCommand="redo">Redo</button>
  </qalma-toolbar>

  <qalma-content class="block min-h-40 p-4 [&_.ProseMirror]:outline-none" />
</qalma-editor>

Insert an image from a file input

insertImage(event: Event): void {
  const input = event.target;

  if (!(input instanceof HTMLInputElement)) {
    return;
  }

  const file = input.files?.item(0);

  if (!file || !file.type.startsWith('image/')) {
    return;
  }

  this.editor.execute('insertImage', {
    src: `/uploads/${encodeURIComponent(file.name)}`,
    alt: file.name.replace(/\.[^.]+$/, ''),
    title: file.name,
    previewSrc: URL.createObjectURL(file),
  });
}
readonly href = signal('https://');

openLinkEditor(): void {
  const link = this.editor.query<LinkState>('link');
  this.href.set(link?.href ?? 'https://');
}

saveLink(): void {
  this.editor.execute('setLink', this.href());
}

removeLink(): void {
  this.editor.execute('unsetLink');
}

Color swatches

readonly colors = ['rgb(190, 18, 60)', 'rgb(67, 56, 202)'] as const;
readonly textColor = computed(() => this.editor.query<string>('textColor'));

setTextColor(color: string): void {
  this.editor.execute('setTextColor', color);
}
@for (color of colors; track color) {
<button type="button" [class.qalma-command-active]="textColor() === color" [disabled]="!editor.canExecute('setTextColor', color)" (mousedown)="$event.preventDefault()" (click)="setTextColor(color)" [style.background-color]="color" [attr.aria-label]="'Set text color ' + color"></button>
}

Slash command pick

pick(option: { command: string; value?: unknown }): void {
  if (!this.editor.execute('deleteSlashCommand')) {
    return;
  }

  this.editor.execute('splitSlashCommandBlock');
  this.editor.execute(option.command, option.value);
}

Inside lists, the docs playground first splits the list item and lifts it out of the list before running the selected block command. That is app-specific menu policy, not library behavior.

Mention pick

pickUser(user: { id: string; label: string }): void {
  this.editor.execute('insertMention', {
    id: user.id,
    label: user.label,
  });
}

Listen to qalma-mention-update and read query<MentionState>('mention') to position and filter your own menu.

Angular Material toolbar

Qalma commands are ordinary controller calls, so Material stays an app dependency:

<mat-toolbar>
  <button mat-icon-button type="button" aria-label="Bold" [class.is-active]="editor.isCommandActive('toggleBold')" [disabled]="!editor.canExecute('toggleBold')" (click)="editor.execute('toggleBold')">
    <mat-icon>format_bold</mat-icon>
  </button>
</mat-toolbar>

Kendo UI toolbar

<kendo-toolbar aria-label="Editor toolbar">
  <kendo-toolbar-button text="Bold" [togglable]="true" [selected]="editor.isCommandActive('toggleBold')" [disabled]="!editor.canExecute('toggleBold')" (click)="editor.execute('toggleBold')" />
</kendo-toolbar>

ng-zorro toolbar

<div role="toolbar" aria-label="Editor toolbar">
  <button nz-button type="button" [nzType]="editor.isCommandActive('toggleBold') ? 'primary' : 'default'" [disabled]="!editor.canExecute('toggleBold')" (click)="editor.execute('toggleBold')">
    <span nz-icon nzType="bold" aria-hidden="true"></span>
    <span class="sr-only">Bold</span>
  </button>
</div>

These snippets show the integration boundary, not a dependency promise for those design systems. Import the components/directives from the versions your application already uses. If you want Qalma's optional Tailwind-first components instead, use @qalma/kit.