Readied Docs
Plugins

Layout Zones

Predefined UI zones where plugins can place React components

Layout Zones

Plugins can place React components into predefined layout zones using context.layout.addComponent().

Available Zones

ZoneLocationTypical Use
editor-status-barBottom of editorStatistics, indicators
editor-header-actionsRight side of note headerToggle buttons, quick actions
editor-toolbarAbove editor contentFormatting tools
panelSide panel areaLarge interactive UI (AI, search)
sidebar-sectionLeft sidebarNavigation, note lists
modalOverlayDialogs, forms
settings-sectionSettings pagePlugin settings UI
note-list-footerBottom of note listNote list actions
command-palette-footerBelow command paletteContextual actions

Zone Diagram

┌─────────────────────────────────────────────────────┐
│ sidebar-section │  editor-header-actions             │
│                 ├───────────────────────────────┐    │
│                 │  editor-toolbar                │    │
│                 ├───────────────────────────────┤    │
│                 │                               │panel│
│                 │  [Editor Content]              │    │
│                 │                               │    │
│                 ├───────────────────────────────┤    │
│ note-list-footer│  editor-status-bar             │    │
└─────────────────────────────────────────────────────┘

Usage

Status Bar Component

import { useState, useEffect } from 'react';
import type { ZoneComponentProps, EditorAPI } from '@readied/plugin-api';

function MyStatus({ meta }: ZoneComponentProps) {
  const editor = meta?.editor as EditorAPI;
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (!editor) return;
    const update = () => setCount(editor.getWordCount());
    update();
    return editor.onDocChanged(update);
  }, [editor]);

  return <span>{count} words</span>;
}

// In activate():
context.layout.addComponent('editor-status-bar', {
  id: 'my-plugin:status',
  component: MyStatus,
  order: 10,
  meta: { editor: context.editor },
});

Header Action Button

function MyToggleButton() {
  const [active, setActive] = useState(false);
  return (
    <button
      className={`note-editor-actions-btn${active ? ' active' : ''}`}
      onClick={() => setActive(!active)}
      title="My Feature"
    >
      <Star size={18} />
    </button>
  );
}

context.layout.addComponent('editor-header-actions', {
  id: 'my-plugin:toggle',
  component: MyToggleButton,
  order: 20,
});

Side Panel

function MyPanel({ meta }: ZoneComponentProps) {
  // Only render when visible
  if (!meta?.visible) return null;

  return (
    <div className="my-panel">
      <h3>My Panel</h3>
      <p>Panel content here</p>
    </div>
  );
}

context.layout.addComponent('panel', {
  id: 'my-plugin:panel',
  component: MyPanel,
  order: 50,
  meta: { context },
});

Ordering

The order property controls position within a zone. Lower numbers appear first (further left in horizontal zones, further up in vertical zones).

RangeConvention
1-9Core app components
10-29Built-in plugins
30-99Community plugins

Cleanup

Always remove your components when the plugin is deactivated:

activate(context) {
  context.layout.addComponent('editor-status-bar', {
    id: 'my-plugin:status',
    component: MyStatus,
    order: 30,
    meta: { editor: context.editor },
  });

  return {
    dispose() {
      context.layout.removeComponent('my-plugin:status');
    },
  };
}