### Basic Setup

`RicosEditor` needs an authenticated context to load plugins, upload media, and resolve relative links. Pick the variant matching your hosting environment.

#### Headless: pass a `wixClient`

When you own the React tree and have an `@wix/sdk` client, pass it directly. The editor uses it for every authenticated request:

```tsx widget
import { createClient, OAuthStrategy } from '@wix/sdk';
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

const wixClient = createClient({
  modules: {},
  auth: OAuthStrategy({ clientId: 'YOUR_CLIENT_ID' }),
});

export default () => (
  // @essential-start
  <RicosEditor
    wixClient={wixClient}
    baseUrl="https://www.example.com/my-site"
    plugins={[pluginImage()]}
    placeholder="Type or insert an image…"
  />
  // @essential-end
);
```

#### In-Wix: pass an `instance` (and `msid`)

When the editor renders inside a Wix-served page, your runtime usually already has a per-session `instance` token and the site's `msid`. Pass them and Ricos builds its own client internally:

```tsx widget
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

export default ({ instance, msid }: { instance: string; msid: string }) => (
  // @essential-start
  <RicosEditor
    instance={instance}
    msid={msid}
    baseUrl="https://www.example.com/my-site"
    plugins={[pluginImage()]}
    placeholder="Type or insert an image…"
  />
  // @essential-end
);
```

#### Always pass `baseUrl`

`baseUrl` is the public URL of the site that owns the content. It's used by link decorations, button plugins, gallery items, and vertical embeds to resolve page-relative hrefs (`/about` → `https://www.example.com/my-site/about`).

The in-Wix flow can auto-fetch this from `/_api/ricos-site-pages/base-url`, but in any headless or cross-origin context you must pass it explicitly — without it, page-relative links in your content render as raw paths.

### Ricos Editor Props

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosEditorProps</span>

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosEditorProps</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>plugins</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#editorpluginoptions"><span class="font-mono">EditorPluginOptions</span></a>[]</code></dt>
<dd><p>Array of editor plugins to enable. Each plugin adds editing capabilities (e.g., image, video, table).</p></dd>
<dt><code><strong>onChange</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#oncontentchangefunction"><span class="font-mono">OnContentChangeFunction</span></a></code></dt>
<dd><p>Fires only when document content changes (not on selection changes).</p></dd>
<dt><code><strong>onSelectionChange</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#onselectionchangefunction"><span class="font-mono">OnSelectionChangeFunction</span></a></code></dt>
<dd><p>Fires when the selection/cursor position changes (including during edits).</p></dd>
<dt><code><strong>placeholder</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Placeholder text displayed when the editor is empty.</p></dd>
</dl>

<div class="pl-4 border-l-2 border-slate-200 mb-4">

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  // @essential-start
  return <RicosEditor placeholder={'Type here!'} />;
  // @essential-end
};
```

</div>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>toolbars</strong><span class="text-slate-400">?</span>: <a href="#toolbarsconfig"><span class="font-mono">ToolbarsConfig</span></a></code></dt>
<dd><p>Toolbars configuration — controls the text formatting toolbar, insert plugin toolbars, plugin context toolbars and the quick-actions menu.</p></dd>
<dt><code><strong>onBusyChange</strong><span class="text-slate-400">?</span>: (isBusy: boolean) =&gt; void</code></dt>
<dd><p>Callback fired when the editor transitions between busy and idle states (e.g., during media uploads).</p></dd>
<dt><code><strong>maxTextLength</strong><span class="text-slate-400">?</span>: number</code></dt>
<dd><p>Maximum number of text characters allowed in the editor. When the limit is reached, further text input is ignored and paste actions are canceled if the result would exceed the limit.</p></dd>
<dt><code><strong>sideBlockComponent</strong><span class="text-slate-400">?</span>: FC&lt;{         id: string;     }&gt;</code></dt>
<dd><p>A React component rendered as a side block alongside the editor content. Receives the block `id` as a prop.</p></dd>
<dt><code><strong>commands</strong><span class="text-slate-400">?</span>: any[]</code></dt>
<dd><p>Custom commands to register with the editor.</p></dd>
<dt><code><strong>historySettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#historysettings"><span class="font-mono">HistorySettings</span></a></code></dt>
<dd><p>Settings for undo/redo history.</p></dd>
<dt><code><strong>onLoad</strong><span class="text-slate-400">?</span>: (editor: any) =&gt; void</code></dt>
<dd><p>Callback fired when the editor finishes loading and is ready for interaction.</p></dd>
<dt><code><strong>autofocus</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#focusposition"><span class="font-mono">FocusPosition</span></a></code></dt>
<dd><p>Controls initial cursor/focus position when the editor mounts.</p></dd>
<dt><code><strong>container</strong><span class="text-slate-400">?</span>: HTMLElement | (() =&gt; HTMLElement | null)</code></dt>
<dd><p>HTML element or factory function that returns the container element for rendering editor popovers and modals.</p></dd>
<dt><code><strong>initialContent</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#ricosdocument"><span class="font-mono">RicosDocument</span></a></code></dt>
<dd><p>Initial content for the editor. Only used during initialization — not a controlled value.</p></dd>
<dt><code><strong>keyboardShortcuts</strong><span class="text-slate-400">?</span>: any[]</code></dt>
<dd><p>Custom keyboard shortcuts to register with the editor.</p></dd>
<dt><code><strong>loadingPlaceholder</strong><span class="text-slate-400">?</span>: React.ReactNode</code></dt>
<dd><p>The placeholder to be displayed while the editor is loading.</p></dd>
<dt><code><strong>tabFocusBehavior</strong><span class="text-slate-400">?</span>: 'edit' | 'container'</code></dt>
<dd><p>Controls focus behavior when tabbing into the editor: - `'edit'`: (default) Immediately enables typing when editor receives keyboard focus. - `'container'`: Shows focus ring but requires Enter/key press to start editing.</p>
<p>Default: <code>'edit'</code></p></dd>
<dt><code><strong>colorManager</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#colorpalettemanager"><span class="font-mono">ColorPaletteManager</span></a></code></dt>
<dd><p>Unified color manager for sharing custom colors across all plugins.</p></dd>
<dt><code><strong>modalSettings</strong><span class="text-slate-400">?</span>: ModalSettings</code></dt>
<dd></dd>
<dt><code><strong>pickMedia</strong><span class="text-slate-400">?</span>: _PublicPickMedia</code></dt>
<dd><p>Pick media implementation used by media-related plugins (e.g., image, gallery).</p></dd>
<dt><code><strong>visitorUploads</strong><span class="text-slate-400">?</span>: _VisitorUploads</code></dt>
<dd><p>Visitor upload settings used by file-upload-capable plugins.</p></dd>
<dt><code><strong>fileUploadSizeLimit</strong><span class="text-slate-400">?</span>: number</code></dt>
<dd><p>Maximum file size limit (in bytes) for uploads.</p></dd>
<dt><code><strong>htmlAttributes</strong><span class="text-slate-400">?</span>: {         autoCapitalize?: string;         spellCheck?: boolean;         autoComplete?: string;         autoCorrect?: string;         tabIndex?: number;         ariaLabel?: string;     }</code></dt>
<dd><p>HTML attributes applied to the editor's content-editable element.</p></dd>
<dt><code><strong>isMobile</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, renders in mobile-optimized mode.</p></dd>
<dt><code><strong>generalLinkSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#generallinksettings"><span class="font-mono">GeneralLinkSettings</span></a></code></dt>
<dd><p>Link settings applied globally to all linkable plugins.</p></dd>
<dt><code><strong>locale</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Two-letter locale code (e.g., `'en'`, `'he'`). Determines the language and text direction of modals and settings.</p></dd>
<dt><code><strong>localeContent</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Locale for content, when different from UI locale. If not defined, `locale` is used.</p></dd>
<dt><code><strong>onError</strong><span class="text-slate-400">?</span>: (error: string) =&gt; void</code></dt>
<dd><p>Callback fired when an error occurs in a Ricos component.</p></dd>
<dt><code><strong>theme</strong><span class="text-slate-400">?</span>: <a href="/api-docs/theming#ricostheme"><span class="font-mono">RicosTheme</span></a></code></dt>
<dd><p>Theme configuration for colors, fonts, and custom styles.</p></dd>
<dt><code><strong>textAlignment</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#textalignment"><span class="font-mono">TextAlignment</span></a></code></dt>
<dd><p>Default text alignment direction.</p></dd>
<dt><code><strong>onAtomicBlockFocus</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#onatomicblockfocus"><span class="font-mono">onAtomicBlockFocus</span></a></code></dt>
<dd><p>Callback fired when an atomic block (plugin) receives focus.</p></dd>
<dt><code><strong>iframeSandboxDomain</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Domain used to render UGC source code in an isolated sandboxed iframe.</p></dd>
<dt><code><strong>textWrap</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, wraps text around left/right-aligned plugins.</p></dd>
<dt><code><strong>debugMode</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#debugmode"><span class="font-mono">DebugMode</span></a>[]</code></dt>
<dd><p>Debug mode flags for enabling development-time diagnostics.</p></dd>
<dt><code><strong>environment</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#environment"><span class="font-mono">Environment</span></a></code></dt>
<dd><p>Theme variant to apply: - `'businessManager'`: BM theme is applied and cannot be overridden. - `'liveSite'`: liveSite theme is applied and can be overridden via `theme`.   'liveSite'</p></dd>
<dt><code><strong>baseUrl</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Base URL of the site, used to resolve page-relative URLs. Used by links, buttons, gallery items, and vertical embeds to construct full hrefs from relative paths (e.g., `/my-page` → `https://example.com/my-site/my-page`).  If not provided, fetched automatically from `/_api/ricos-site-pages/base-url`.</p></dd>
<dt><code><strong>biSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#bisettings"><span class="font-mono">BISettings</span></a></code></dt>
<dd><p>Business intelligence configuration for analytics and tracking.</p></dd>
<dt><code><strong>wixClient</strong><span class="text-slate-400">?</span>: WixClient</code></dt>
<dd><p>Authenticated `WixClient` from `@wix/sdk`. When provided, takes priority over `instance` for authorizing every request the editor makes (media uploads, link previews, etc.). Use this in headless apps where you own the React tree.</p></dd>
<dt><code><strong>instance</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Per-session Wix instance token. Used when no `wixClient` is provided — Ricos builds an internal client from it. Typically set by Wix-served host pages.</p></dd>
<dt><code><strong>msid</strong><span class="text-slate-400">?</span>: string</code></dt>
<dd><p>Metasite ID. Scopes storage and CDN traffic to the owning site when no `wixClient` is given.</p></dd>
<dt><code><strong>shouldNormalizeRichContent</strong><span class="text-slate-400">?</span>: boolean</code></dt>
<dd><p>When `true`, normalises rich content via `normalizeRicosDocument` before rendering. Set to `false` to skip normalisation when content is already known to be valid.   true</p></dd>
<dt><code><strong>mediaSettings</strong><span class="text-slate-400">?</span>: MediaSettings</code></dt>
<dd><p>Media-upload, capture, and resolution settings shared across media plugins.</p></dd>
<dt style="opacity: 0.5;"><code><strong>linkSettings</strong><span class="text-slate-400">?</span>: <a href="/api-docs/related-types#linksettings"><span class="font-mono">LinkSettings</span></a></code></dt>
<dd><p><em>Deprecated.</em> Use `generalLinkSettings` instead.</p></dd>
<dt style="opacity: 0.5;"><code><strong>experiments</strong><span class="text-slate-400">?</span>: AvailableExperiments</code></dt>
<dd><p><em>Deprecated.</em> Not used at runtime.</p></dd>
</dl>

### Ricos Editor Imperative API

#### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">RicosEditorAPI</span>

Imperative API for controlling a mounted `RicosEditor`. Accessed via a React ref — `const editorRef = useRef<RicosEditorType>(null)` — and read inside event handlers (publish flows, programmatic content swaps, focus/undo/redo controls). For UI that needs to react to editor state in render — toolbar enable/disable, undo-stack indicators — prefer the reactive hooks (`useToolbarProps`, `useUndoState`) over polling these methods.

<code class="font-mono text-sm"><span class="text-blue-700">interface</span> RicosEditorAPI</code>

<dl class="pl-4 border-l-2 border-slate-200">
<dt><code><strong>focus</strong>: (focusPosition?: FocusPosition): void;</code></dt>
<dd><p>Focuses the editor. Optionally moves the cursor to `focusPosition`.</p></dd>
<dt><code><strong>blur</strong>: (): void;</code></dt>
<dd><p>Removes focus from the editor.</p></dd>
<dt><code><strong>undo</strong>: (): void;</code></dt>
<dd><p>Undoes the last editor action.</p></dd>
<dt><code><strong>redo</strong>: (): void;</code></dt>
<dd><p>Redoes the last undone action.</p></dd>
<dt><code><strong>getContent</strong>: (postId?: string, forPublish?: boolean, shouldRemoveErrorBlocks?: boolean): Promise&lt;RicosDocument&gt;;</code></dt>
<dd><p>Returns the current content as a `RicosDocument`. Resolves after any pending async operations.</p></dd>
<dt><code><strong>getContentPromise</strong>: (props: {         flush?: boolean;         publishId?: string;     } | undefined): Promise&lt;RicosDocument&gt;;</code></dt>
<dd><p>Returns a promise of the current content. `publishId` — used for BI (`onPublish` event), set it when called before publishing. `flush` — triggers editor blur to force pending changes (e.g., Android composition mode) before reading content.</p></dd>
<dt><code><strong>getContentTraits</strong>: (): {         isEmpty: boolean;         isContentChanged: boolean;         isLastChangeEdit: boolean;     };</code></dt>
<dd><p>Returns content traits: whether the editor is empty, content has changed, and the last change was an edit.</p></dd>
<dt><code><strong>setContent</strong>: (content: RicosDocument): void;</code></dt>
<dd><p>Replaces the editor content. Imperative replacement for the removed `injectedContent` prop.</p></dd>
<dt><code><strong>getEditorContainer</strong>: (): HTMLElement | undefined;</code></dt>
<dd><p>Returns the DOM element wrapping the editor's content-editable, if mounted.</p></dd>
<dt><code><strong>openModal</strong>: (options: {         type: 'AI_IMAGE' | 'INLINE_AI' | 'SHORTCUTS_SHEET' | 'AI_WIDGET';     }) =&gt; void</code></dt>
<dd><p>Opens a built-in modal (AI image generation, inline AI, keyboard shortcuts sheet, or AI widget).</p></dd>
<dt><code><strong>useToolbarProps</strong>: (type: UseToolbarPropsType): ToolbarPropsResult;</code></dt>
<dd><p>Subscribe to the reactive button list for a toolbar. Re-runs on selection and content changes, so consumers rendering a custom toolbar stay in sync automatically. Must be called inside a React component.</p></dd>
<dt><code><strong>useInlineAiState</strong>: (invocationId: string) =&gt; unknown</code></dt>
<dd></dd>
<dt><code><strong>prePublish</strong>: (): Promise&lt;RicosDocument&gt;;</code></dt>
<dd><p>Runs plugin pre-publish hooks (e.g. poll sync) and returns the content.</p></dd>
<dt><code><strong>useUndoState</strong>: () =&gt; {         isUndoStackEmpty: boolean;         isRedoStackEmpty: boolean;     }</code></dt>
<dd><p>Reactive undo/redo state. Must be called inside a React component.</p></dd>
</dl>

```tsx widget
import type { RicosDocument, RicosEditorType } from '@wix/ricos';
import { pluginImage, RicosEditor } from '@wix/ricos';
import React from 'react';

export default () => {
  const editorRef = React.useRef<RicosEditorType>(null);

  // @essential-start
  const UndoButtons = () => {
    const { isUndoStackEmpty, isRedoStackEmpty } = editorRef.current?.useUndoState() ?? {
      isUndoStackEmpty: true,
      isRedoStackEmpty: true,
    };
    return (
      <>
        <button onClick={() => editorRef.current?.undo()} disabled={isUndoStackEmpty} type="button">
          Undo
        </button>
        <button onClick={() => editorRef.current?.redo()} disabled={isRedoStackEmpty} type="button">
          Redo
        </button>
      </>
    );
  };

  const handlePublish = async () => {
    const document = await editorRef.current?.prePublish();
    alert(`Publishing ${document?.nodes.length ?? 0} nodes`);
  };

  const handleReset = () => editorRef.current?.setContent(initialContent);

  return (
    <>
      <RicosEditor
        ref={editorRef}
        initialContent={initialContent}
        plugins={[pluginImage()]}
        placeholder="Edit me — drag an image in or use the toolbar"
      />
      <UndoButtons />
      <button onClick={handlePublish} type="button">
        Publish
      </button>
      <button onClick={handleReset} type="button">
        Reset
      </button>
    </>
  );
  // @essential-end
};

const initialContent: RicosDocument = {
  nodes: [
    {
      type: 'PARAGRAPH',
      id: 'p1',
      nodes: [
        {
          type: 'TEXT',
          id: '',
          nodes: [],
          textData: { text: 'Edit, undo, publish, reset.', decorations: [] },
        },
      ],
    },
    {
      type: 'IMAGE',
      id: 'img1',
      nodes: [],
      imageData: {
        containerData: {
          width: { size: 'CONTENT' },
          alignment: 'CENTER',
          textWrap: true,
        },
        image: {
          src: { url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg' },
          width: 5120,
          height: 3413,
        },
        altText: 'A tree during sunset.',
      },
    },
  ],
};
```


## Toolbars

Ricos ships with four consumer-facing toolbars, all configured through a single `toolbars` prop on `RicosEditor`:

- **Text toolbar** — formatting controls shown when the user selects text (floating by default, or rendered into a container)
- **Insert toolbar** — the inline plus button on empty lines and the footer plugin bar
- **Plugin toolbar** — the bubble that appears when a plugin node or decoration (e.g. a link) is selected
- **Quick actions** — the slash-command menu triggered by typing `/`

Each is optional — omit a section to keep its default behaviour. Fields are grouped by concept, not by the low-level rendering variant, so the same config drives desktop and mobile without platform-specific branches.

##### <span style="font-weight: 700;" class="rounded-lg border-[1px] border-blue-300 font-mono border-none bg-blue-100 px-2 py-1 leading-[18px] tracking-[0.0175em] font-bold text-slate-900">ToolbarsConfig</span>

Configuration for the editor's consumer-facing toolbars — text formatting, insert plugin menus, plugin context bubbles and quick actions.  Fields are grouped by concept (`text`, `insert`, `plugin`, `quickActions`) so the same config drives desktop and mobile without platform-specific branching.

<pre class="block py-4 px-4 my-2 overflow-auto rounded border-none bg-slate-200 font-mono text-sm leading-6 tracking-wide text-slate-900"><code><span class="text-blue-700">type</span> ToolbarsConfig = {     text?: TextToolbarConfig;     insert?: InsertToolbarConfig;     plugin?: PluginToolbarConfig;     quickActions?: <a href="#quickactionsconfig"><span class="font-mono">QuickActionsConfig</span></a>; }</code></pre>

### Text toolbar

Two mutually exclusive modes: `floating` (above the selection) and `static` (rendered into a DOM container the consumer owns).

#### Floating mode with custom buttons

Filter the default button list and register your own components:

```tsx widget
import { FORMATTING_BUTTONS, RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Floating text toolbar with:
 * - `buttons` callback filtering out CODE_BLOCK from defaults and appending
 *   a custom button name
 * - `customButtons` registering a React component for that name
 *
 * The callback receives `defaultButtons` already resolved to the platform's
 * default text button ids.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        text: {
          mode: 'floating',
          buttons: ({ defaultButtons }) => [
            ...defaultButtons.filter(id => id !== FORMATTING_BUTTONS.CODE_BLOCK),
            'MY_INFO',
          ],
          customButtons: {
            MY_INFO: {
              component: () => (
                // oxlint-disable-next-line
                <div onClick={() => console.log('info!')} style={{ padding: '0 8px' }}>
                  ℹ︎
                </div>
              ),
            },
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'בחר טקסט כדי לראות את סרגל הכלים הצף!'
          : 'Select text to see the floating toolbar!'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

The `buttons` callback receives `defaultButtons` — the platform-resolved list of built-in button ids. Return the desired order; unknown ids are dropped silently. Use `customButtons` to register React components keyed by the ids you reference.

#### Static mode

Render the text toolbar into a container outside the editor — useful when the page already has a fixed toolbar area:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Static text toolbar rendered into a container the consumer owns.
 *
 *   toolbars: { text: { mode: 'static', container, alignment: 'left' } }
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  const [toolbarContainer, setToolbarContainer] = React.useState<HTMLDivElement | null>(null);

  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <div
        ref={setToolbarContainer}
        style={{ minHeight: 48, border: '1px dashed #ccc', padding: 8 }}
      />
      {/* @essential-start */}
      <RicosEditor
        initialContent={content}
        plugins={editorPlugins}
        toolbars={
          toolbarContainer
            ? { text: { mode: 'static', container: toolbarContainer, alignment: 'left' } }
            : undefined
        }
        onChange={setContent}
        placeholder={locale === 'he' ? 'הוסף קצת טקסט!' : 'Add some text!'}
        isMobile={isMobile}
        environment={environment}
        locale={locale}
        localeContent={localeContent}
      />
      {/* @essential-end */}
    </div>
  );
};
```

Set `container` to any `HTMLElement`. `alignment` controls horizontal positioning inside that container.

#### Mobile

The same config drives both platforms — the runtime picks the mobile variant automatically when `isMobile` is on. `defaultButtons` in the callback is already resolved to the platform's defaults (headings inline, no font family / AI controls, `AddPlugin` inserted), so one filter works everywhere:

```tsx widget
import { FORMATTING_BUTTONS, RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Text formatting toolbar on mobile.
 *
 * On mobile the runtime automatically renders the mobile variant of whichever
 * `text.mode` you picked. The same config drives both platforms:
 *
 * - Headings render as individual buttons on mobile (no dropdown).
 * - Font family / AI / indent controls are not part of the mobile defaults.
 * - `AddPlugin` appears inline at the start of the mobile toolbar.
 *
 * The `buttons` callback is invoked once per platform with the
 * platform-resolved `defaultButtons`, so the same filter/reorder logic
 * applies to both without extra code.
 *
 * Toggle the **isMobile** switch in the dev panel to see the desktop variant
 * — this example forces mobile on mount.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
    environment: 'businessManager',
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      // Force mobile so the mobile text toolbar is visible without toggling.
      isMobile
      toolbars={{
        text: {
          mode: 'floating',
          // Same callback on desktop and mobile — `defaultButtons` is
          // platform-resolved. Drop the CODE_BLOCK button on both platforms.
          buttons: ({ defaultButtons }) =>
            defaultButtons.filter(id => id !== FORMATTING_BUTTONS.CODE_BLOCK),
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'בחירת טקסט — סרגל הכלים לעיצוב לנייד מופיע.'
          : 'Select text — the mobile formatting toolbar appears.'
      }
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Insert toolbar

Two surfaces share a single `buttons` list: the inline plus button's popup menu and the footer bar.

#### Footer with pinned plugins

Pin a subset of installed plugins to the footer. The rest stay accessible via the "More" menu:

```tsx widget
import { INSERT_PLUGIN_BUTTONS, RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Footer insert toolbar pinning only a subset of installed plugins.
 *
 * All plugins remain installed; only the listed ones get a dedicated footer
 * button. The rest live under the "More" menu.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        insert: {
          footer: {
            pluginsToDisplayInToolbar: [
              INSERT_PLUGIN_BUTTONS.IMAGE,
              INSERT_PLUGIN_BUTTONS.VIDEO,
              INSERT_PLUGIN_BUTTONS.GIF,
            ],
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'רק תמונה, סרטון וגיף מוצמדים למטה!'
          : 'Only image, video & gif are pinned below!'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

Use the `INSERT_PLUGIN_BUTTONS` constants to reference built-in plugin buttons by id.

#### Custom plus button trigger

Override what happens when the user clicks the inline plus button. `defaultTrigger` opens the built-in menu — call it to preserve the default, skip it to replace entirely:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Custom behaviour for the inline plus button.
 *
 * `insert.plusButton.onTrigger` receives `defaultTrigger` — call it to keep
 * the built-in open-menu behaviour, skip it to replace entirely, or wrap
 * with extra logic (tracking, permission gates, a custom panel, …).
 *
 * This example shows a "track + fall through" pattern. Click the plus button
 * on an empty line: you'll see a log line and then the default menu opens.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        insert: {
          plusButton: {
            onTrigger: defaultTrigger => {
              // oxlint-disable-next-line
              console.log('[plusButton] clicked — tracking, then opening menu');
              defaultTrigger?.();
            },
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'לחץ על כפתור הפלוס שליד שורה ריקה!'
          : 'Click the plus button next to an empty line!'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Plugin toolbar

The `buttons` callback is invoked every time a plugin node or decoration is focused, with a discriminated context: `{ type: 'node', nodeType }` or `{ type: 'decoration', decoration }`.

#### Filter buttons on a specific plugin

Drop or reorder buttons only for the plugin you care about, pass through everything else:

```tsx widget
import { RicosEditor, RicosPluginToolbarNames } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Filter and reorder buttons on a specific plugin node's toolbar.
 *
 * `plugin.buttons` receives a discriminated ctx. When `type === 'node'`, the
 * `nodeType` tells you which plugin's toolbar is about to render. Return an
 * array of ids in the desired order — unknown ids are silently dropped.
 *
 * This example drops the "size" button from the IMAGE plugin toolbar and
 * moves "alignment" to the front, leaving other plugin toolbars untouched.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.imageContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        plugin: {
          buttons: ctx => {
            if (ctx.type === 'node' && ctx.nodeType === RicosPluginToolbarNames.IMAGE) {
              const alignmentId = ctx.defaultButtons.find(id =>
                id.toLowerCase().includes('alignment')
              );
              const rest = ctx.defaultButtons.filter(
                id => id !== alignmentId && !id.toLowerCase().includes('size')
              );
              return alignmentId ? [alignmentId, ...rest] : rest;
            }
            return ctx.defaultButtons;
          },
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'לחץ על התמונה - סרגל הכלים שלה מותאם אישית.'
          : 'Click the image — its toolbar is customized.'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

The `RicosPluginToolbarNames` enum provides typed names for every built-in plugin / decoration.

#### Hide a decoration bubble entirely

Return `[]` from the callback to suppress the toolbar for that selection — this is how you hide the LINK bubble:

```tsx widget
import { RicosEditor, RicosPluginToolbarNames } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Hide the LINK bubble toolbar via `plugin.buttons`.
 *
 * Links are surfaced as a *decoration* plugin toolbar. Returning `[]` for the
 * matching ctx hides the bubble entirely.
 *
 * Click inside a link in the content — the usual bubble no longer appears.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        plugin: {
          buttons: ctx =>
            ctx.type === 'decoration' && ctx.decoration === RicosPluginToolbarNames.LINK
              ? []
              : ctx.defaultButtons,
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'הקלד טקסט, הוסף קישור ולאחר מכן לחץ עליו - ללא בועה.'
          : 'Type text, add a link, then click it — no bubble.'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Quick actions

The slash-command menu. Pass `placeholder` to override the prompt shown in the slash input (default: `'Type /'`), or `disabled: true` to remove the menu entirely:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Custom quick-actions placeholder.
 *
 * Type `/` in an empty line to open the slash menu with the custom placeholder.
 * Set `disabled: true` to turn off the slash menu entirely.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        quickActions: {
          placeholder: 'Type / to add a block',
        },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he'
          ? 'לחץ על / בשורה ריקה כדי לפתוח את תפריט הפעולות המהירות'
          : 'Press / on an empty line to open the quick-actions menu'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### Disabling everything

Set `disabled: true` on each section to remove all consumer-facing chrome — useful when rendering fully external toolbars:

```tsx widget
import { RicosEditor } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * Minimal editor — every consumer-facing toolbar turned off.
 *
 * Shows how `*.disabled: true` combines:
 *   - `text.disabled: true`              — no floating/static/mobile formatting bar
 *   - `insert.plusButton.disabled: true` — no inline plus button
 *   - `insert.footer.disabled: true`     — no footer plugin bar
 *   - `plugin.disabled: true`            — no plugin bubble toolbars (also hides LINK)
 *   - `quickActions.disabled: true`      — no slash menu
 *
 * The result is a bare content-editable with no chrome — useful when the
 * consumer renders their own toolbars externally.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  return (
    // @essential-start
    <RicosEditor
      initialContent={content}
      plugins={editorPlugins}
      toolbars={{
        text: { mode: 'floating', disabled: true },
        insert: {
          plusButton: { disabled: true },
          footer: { disabled: true },
        },
        plugin: { disabled: true },
        quickActions: { disabled: true },
      }}
      onChange={setContent}
      placeholder={
        locale === 'he' ? 'עורך פשוט - ללא סרגלי כלים כלל.' : 'A bare editor — no toolbars at all.'
      }
      isMobile={isMobile}
      environment={environment}
      locale={locale}
      localeContent={localeContent}
    />
    // @essential-end
  );
};
```

### External toolbars via `useToolbarProps`

When the default placements don't fit your design, hide the internal toolbars and render buttons wherever you want — `RicosEditorAPI.useToolbarProps(type)` returns the current button state and re-runs on selection and content changes:

```tsx widget
import type { RicosEditorAPI } from '@wix/ricos';
import { RicosEditor, ToolbarType } from '@wix/ricos';
import React from 'react';
import {
  CONTENT_FIXTURE_IDS,
  ExternalToolbar,
  preconfiguredEditorPlugins,
  useDevContent,
  useDevSettings,
} from 'ricos-dev-utils/next';

/**
 * External formatting toolbar — render your own toolbar DOM off to the side.
 *
 * The pattern is:
 *   1. Hide the editor's internal toolbars via `toolbars`.
 *   2. Read reactive button state via `editorApi.useToolbarProps(type)`.
 *   3. Render the buttons wherever the consumer wants.
 *
 * The hook re-runs on selection and content changes, so the external toolbar
 * stays in sync automatically.
 */
export default () => {
  const [content, setContent] = useDevContent({
    contentId: CONTENT_FIXTURE_IDS.emptyContent,
  });
  const { isMobile, environment, locale, localeContent, editorPlugins } = useDevSettings({
    editorPlugins: preconfiguredEditorPlugins,
  });

  const [editorApi, setEditorApi] = React.useState<RicosEditorAPI | null>(null);

  return (
    <>
      {editorApi && <ConsumerToolbar editorApi={editorApi} />}
      {/* @essential-start */}
      <RicosEditor
        initialContent={content}
        plugins={editorPlugins}
        toolbars={{
          // Hide everything the editor would render itself…
          text: { mode: 'floating', disabled: true },
          insert: { plusButton: { disabled: true }, footer: { disabled: true } },
        }}
        onChange={setContent}
        placeholder={'Select text — the buttons above will activate.'}
        ref={setEditorApi}
        isMobile={isMobile}
        environment={environment}
        locale={locale}
        localeContent={localeContent}
      />
    </>
  );
};

const ConsumerToolbar: React.FC<{ editorApi: RicosEditorAPI }> = ({ editorApi }) => {
  // …then read button state from the hook and render it ourselves.
  const formatting = editorApi.useToolbarProps(ToolbarType.FORMATTING);
  const plugin = editorApi.useToolbarProps(ToolbarType.PLUGIN);

  const buttons =
    'buttons' in plugin && Object.keys(plugin.buttons).length ? plugin.buttons : formatting.buttons;

  return <ExternalToolbar buttons={buttons} />;
      // @essential-end
};
```

Pass a `ToolbarType` (e.g. `ToolbarType.FORMATTING`, `ToolbarType.PLUGIN`) to pick which button set to read. Each returned button exposes `onClick`, `isActive`, `isDisabled`, `getIcon`, `getLabel`, and more — see `ToolbarButtonProps` for the full shape.
