Angular

    Angular Rich Text Editor

    Scribe Editor is the lightweight, zero-dependency WYSIWYG editor for Angular. Because it exposes a plain Scribe.init(element) DOM API, it drops into any Angular component with a single @ViewChild — no official adapter package needed.

    $npm install scribejs-editor
    < 50KB
    Gzipped bundle
    0
    Runtime dependencies
    1 component
    To integrate

    Why use Scribe in your Angular app?

    Under 50KB gzipped

    Zero runtime dependencies. No ProseMirror, no Quill core, no Angular wrapper package to ship — your bundle stays lean.

    Plain DOM API

    Scribe.init() takes any HTMLElement, so @ViewChild + ElementRef is all you need. No NgZone wrestling, no custom adapters.

    Forms-ready

    Wrap it as a ControlValueAccessor once and it drops into [(ngModel)] and reactive formControlName like any native input.

    Built-in XSS sanitization

    Safe paste handling from Word and Google Docs out of the box. No DomSanitizer gymnastics or extra libraries required.

    Note: Scribe has no official Angular package. It exposes a single framework-agnostic, DOM-based API (Scribe.init(element, options) and editor.destroy()). The component and ControlValueAccessor below are standard Angular wrapper patterns around that DOM API — which works because Angular gives you the raw element through ElementRef.

    Basic Angular Component

    Grab the host element with @ViewChild + ElementRef, initialize Scribe in ngOnInit, and tear it down in ngOnDestroy. That is the entire lifecycle.

    import {
      Component,
      ElementRef,
      ViewChild,
      OnInit,
      OnDestroy,
    } from '@angular/core';
    import { Scribe } from 'scribejs-editor';
    import 'scribejs-editor/dist/scribe.css';
    
    @Component({
      selector: 'app-rich-text-editor',
      standalone: true,
      template: '<div #host></div>',
    })
    export class RichTextEditorComponent implements OnInit, OnDestroy {
      // ViewChild gives us the raw DOM element Scribe needs.
      @ViewChild('host', { static: true }) host!: ElementRef<HTMLElement>;
    
      private editor?: ReturnType<typeof Scribe.init>;
    
      ngOnInit(): void {
        // Scribe is framework-agnostic — it just needs a DOM element.
        this.editor = Scribe.init(this.host.nativeElement, {
          toolbar: 'floating',       // or 'fixed' | 'none'
          placeholder: 'Start writing...',
          onChange: (html: string) => console.log(html),
        });
      }
    
      ngOnDestroy(): void {
        // Always tear down on destroy to avoid leaking listeners.
        this.editor?.destroy();
      }
    }

    ControlValueAccessor for ngModel & Reactive Forms

    Implement ControlValueAccessor so Scribe behaves like a native form control. Push edits through Scribe's onChange and apply external values with setHTML() inside writeValue().

    import {
      Component,
      ElementRef,
      ViewChild,
      OnInit,
      OnDestroy,
      forwardRef,
    } from '@angular/core';
    import {
      ControlValueAccessor,
      NG_VALUE_ACCESSOR,
    } from '@angular/forms';
    import { Scribe } from 'scribejs-editor';
    
    @Component({
      selector: 'app-scribe-control',
      standalone: true,
      template: '<div #host></div>',
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => ScribeControlComponent),
          multi: true,
        },
      ],
    })
    export class ScribeControlComponent
      implements ControlValueAccessor, OnInit, OnDestroy
    {
      @ViewChild('host', { static: true }) host!: ElementRef<HTMLElement>;
    
      private editor?: ReturnType<typeof Scribe.init>;
      private onChange: (value: string) => void = () => {};
      private onTouched: () => void = () => {};
    
      ngOnInit(): void {
        this.editor = Scribe.init(this.host.nativeElement, {
          toolbar: 'fixed',
          onChange: (html: string) => {
            // Push every edit back into the form model.
            this.onChange(html);
            this.onTouched();
          },
        });
      }
    
      ngOnDestroy(): void {
        this.editor?.destroy();
      }
    
      // --- ControlValueAccessor ---
      writeValue(value: string | null): void {
        // Angular sets the initial / patched value here.
        this.editor?.setHTML(value ?? '');
      }
    
      registerOnChange(fn: (value: string) => void): void {
        this.onChange = fn;
      }
    
      registerOnTouched(fn: () => void): void {
        this.onTouched = fn;
      }
    
      setDisabledState(isDisabled: boolean): void {
        // TODO(verify): Scribe has no documented Angular-specific disable API.
        // Use a standard contenteditable toggle on the host element instead.
        this.host.nativeElement
          .querySelector('[contenteditable]')
          ?.setAttribute('contenteditable', String(!isDisabled));
      }
    }

    Using it in a form

    Once the wrapper implements ControlValueAccessor, it works with both template-driven [(ngModel)] and reactive formControlName bindings.

    <!-- template-driven forms -->
    <app-scribe-control [(ngModel)]="body"></app-scribe-control>
    
    <!-- reactive forms -->
    <form [formGroup]="form">
      <app-scribe-control formControlName="body"></app-scribe-control>
    </form>

    Everything you need, nothing you don't

    Bold, italic, underline, strikethrough
    Headings H1–H6
    Ordered & unordered lists
    Links with inline editing
    Floating toolbar on selection
    Fixed toolbar mode
    Paste safe from Word & Google Docs
    Built-in XSS sanitization
    Works with [(ngModel)] & reactive forms
    Plugin system for extensibility
    Full TypeScript types included
    MIT license — free forever

    Add a rich text editor to Angular in minutes.

    No license keys. No CDN links. No account. Just npm install scribejs-editor and wire up one component.

    Angular + Scribe — common questions

    How do I add a rich text editor to an Angular app?

    Install scribejs-editor, expose the host element with @ViewChild and ElementRef, then call Scribe.init(this.host.nativeElement) in ngOnInit and editor.destroy() in ngOnDestroy. Scribe is framework-agnostic and only needs a DOM element, so no Angular-specific adapter is required.

    Is there an official Angular package for Scribe?

    No. Scribe ships a single framework-agnostic, DOM-based API (Scribe.init / editor.destroy). The Angular component and ControlValueAccessor shown here are standard wrapper patterns you write yourself around that API — there is no official @scribejs/angular adapter.

    How do I bind Scribe to ngModel or reactive forms?

    Implement ControlValueAccessor on your wrapper component and register it with NG_VALUE_ACCESSOR. Push editor changes through onChange in Scribe’s onChange callback, and apply incoming values with editor.setHTML() inside writeValue(). The component then works with [(ngModel)] and formControlName.

    Why call destroy() in ngOnDestroy?

    Scribe attaches DOM listeners and toolbar elements when it initializes. Calling editor.destroy() in ngOnDestroy removes them so the editor is cleaned up when Angular tears down the component, preventing leaked listeners and duplicate toolbars on route changes.