Skip to main content

Overview

The Elements API provides a high-level, unified interface for working with elements inside blocks. It abstracts away Slate.js complexity, making it easier to manipulate block content without deep knowledge of the underlying editor structure.
The Elements API follows CRUD patterns familiar to most developers: create (insert), read (get), update, and delete.

Accessing Elements API

Elements API methods are available in two ways: All methods are available directly on the editor instance:
import { useYooptaEditor } from '@yoopta/editor';

function MyComponent() {
  const editor = useYooptaEditor();

  // Use methods directly on editor
  editor.insertElement({ /* ... */ });
  editor.getElement({ /* ... */ });
  editor.updateElement({ /* ... */ });
  editor.deleteElement({ /* ... */ });
}

2. Through Elements Namespace (For Backwards Compatibility)

You can also import and use the Elements namespace:
import { Elements } from '@yoopta/editor';

// Methods are available on Elements namespace
Elements.insertElement(editor, { /* ... */ });
Elements.getElement(editor, { /* ... */ });
Elements.updateElement(editor, { /* ... */ });
The namespace export is provided for backwards compatibility. We recommend using methods directly on the editor instance for better TypeScript support and cleaner code.

Why Elements API?

  • No Slate.js Knowledge Required: Work with elements using simple, semantic methods
  • Type Safety: Full TypeScript support with proper type inference
  • Consistent API: All methods follow the same pattern and naming convention
  • Better DX: Excellent autocomplete and inline documentation

Core Methods

The Elements API consists of three main categories:

CRUD Operations

Retrieval Methods

Quick Example

import { useYooptaEditor } from '@yoopta/editor';

function MyComponent() {
  const editor = useYooptaEditor();

  const handleAddItem = () => {
    // Insert new accordion item
    editor.insertElement({
      blockId: 'accordion-1',
      type: 'accordion-list-item',
      props: { isExpanded: true },
      children: [
        editor.y('accordion-list-item-heading', {
          children: [editor.y.text('New Item')]
        }),
        editor.y('accordion-list-item-content')
      ],
      at: 'next',
      focus: true
    });
  };

  const handleToggleItem = (itemPath: number[]) => {
    // Get current item
    const item = editor.getElement({
      blockId: 'accordion-1',
      type: 'accordion-list-item',
      path: itemPath
    });

    // Toggle expanded state
    if (item) {
      editor.updateElement({
        blockId: 'accordion-1',
        type: 'accordion-list-item',
        props: { isExpanded: !item.props?.isExpanded },
        path: itemPath
      });
    }
  };

  return (
    // Your component JSX
  );
}

Common Use Cases

Working with Accordion

// Get all accordion items
const items = editor.getElements({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
});

// Find expanded item
const expandedItem = editor.getElement({
  blockId: 'accordion-1',
  match: (el) => el.type === 'accordion-list-item' && el.props?.isExpanded,
});

// Delete specific item
editor.deleteElement({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  path: [0, 2], // Third item
});

Working with Tabs

// Add new tab
const tabId = generateId();

editor.insertElement({
  blockId: 'tabs-1',
  type: 'tabs-item-heading',
  props: { id: tabId },
  children: [editor.y.text('New Tab')],
  at: 'next',
});

editor.insertElement({
  blockId: 'tabs-1',
  type: 'tabs-item-content',
  props: { referenceId: tabId },
  at: 'end',
});

// Get active tab
const activeTab = editor.getElement({
  blockId: 'tabs-1',
  match: (el) => el.type === 'tabs-item-heading' && el.props?.active,
});

Working with Tables

// Update cell properties
editor.updateElement({
  blockId: 'table-1',
  type: 'table-data-cell',
  props: { width: 300, asHeader: true },
  path: [0, 0, 0], // First cell in first row
});

// Get all rows
const rows = editor.getElements({
  blockId: 'table-1',
  type: 'table-row',
});

// Insert new row
editor.insertElement({
  blockId: 'table-1',
  type: 'table-row',
  children: [
    editor.y('table-data-cell', { children: [editor.y.text('')] }),
    editor.y('table-data-cell', { children: [editor.y.text('')] }),
  ],
  at: 'next',
});

Working with Inline Elements (Links, Mentions)

// Insert link at current selection
editor.insertElement({
  blockId: 'paragraph-1',
  type: 'link',
  props: { url: 'https://example.com', target: '_blank' },
  text: 'Click here',
  at: 'selection',
});

// Update link URL and text
editor.updateElement({
  blockId: 'paragraph-1',
  type: 'link',
  props: { url: 'https://new-url.com' },
  text: 'Updated link text',
});

// Remove link but keep text (unwrap)
editor.deleteElement({
  blockId: 'paragraph-1',
  type: 'link',
  mode: 'unwrap', // Keeps text, removes link wrapper
});

// Get all links in a block
const links = editor.getElements({
  blockId: 'paragraph-1',
  type: 'link',
});

// Find specific link by URL
const specificLink = editor.getElement({
  blockId: 'paragraph-1',
  match: (el) => el.type === 'link' && el.props?.url === 'https://example.com',
});

Working with Element Paths

// Get element path
const elementPath = editor.getElementPath({
  blockId: 'accordion-1',
  element: accordionItem,
});

// Get parent element path
const parentPath = editor.getParentElementPath({
  blockId: 'accordion-1',
  element: accordionContent,
});

// Get element with its path (entry)
const entry = editor.getElementEntry({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  path: [0, 1],
});

if (entry) {
  const [element, path] = entry;
  console.log('Element:', element);
  console.log('Path:', path);
}

// Get element children
const children = editor.getElementChildren({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  path: [0, 1],
});

Type Definitions

Common Types

// Element path specification
// Can be a specific Slate path, or a semantic path
type ElementPath = number[] | 'selection' | 'first' | 'last';

// Element matcher function for custom queries
type ElementMatcher = (element: SlateElement) => boolean;

Element Structure

Elements in Yoopta Editor follow this structure:
type SlateElement = {
  id?: string;
  type: string;
  props?: Record<string, unknown>;
  children: (SlateElement | SlateElementTextNode)[];
};

Available Type Exports

All types are exported from @yoopta/editor:
import type {
  ElementPath,
  ElementMatcher,
  InsertElementOptions,
  UpdateElementOptions,
  DeleteElementOptions,
  GetElementOptions,
  GetElementsOptions,
  GetElementEntryOptions,
  GetElementPathOptions,
  GetElementChildrenOptions,
  IsElementEmptyOptions,
} from '@yoopta/editor';

Path Options Explained

The ElementPath type supports multiple ways to specify element location:
  • number[]: Direct Slate path (e.g., [0, 1, 2])
  • 'selection': Element at current cursor/selection
  • 'first': First element of the specified type
  • 'last': Last element of the specified type
// Direct path
editor.getElement({ blockId: 'accordion-1', type: 'item', path: [0, 1] });

// First element of type
editor.getElement({ blockId: 'accordion-1', type: 'item', path: 'first' });

// Last element of type
editor.getElement({ blockId: 'accordion-1', type: 'item', path: 'last' });

// Element at selection
editor.getElement({ blockId: 'accordion-1', type: 'item', path: 'selection' });

Best Practices

Always use blockId to specify which block you’re working with. Element operations are scoped to specific blocks.

1. Use Specific Paths When Possible

// ✅ Good - specific path
editor.updateElement({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  props: { isExpanded: true },
  path: [0, 1],
});

// ❌ Avoid - relies on selection
editor.updateElement({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  props: { isExpanded: true },
});

2. Use Matchers for Dynamic Queries

// Find element by specific condition
const activeItem = editor.getElement({
  blockId: 'tabs-1',
  match: (el) => el.type === 'tabs-item-heading' && el.props?.active === true,
});

3. Check Existence Before Operations

const element = editor.getElement({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  path: itemPath,
});

if (element) {
  editor.updateElement({
    blockId: 'accordion-1',
    type: 'accordion-list-item',
    props: { isExpanded: !element.props?.isExpanded },
    path: itemPath,
  });
}

4. Use Helper Methods

// Check if content is empty before operations
const isEmpty = editor.isElementEmpty({
  blockId: 'accordion-1',
  type: 'accordion-list-item-content',
  path: [0, 1, 1],
});

if (isEmpty) {
  // Handle empty content
}

5. Handle Inline vs Block Elements

// Inline elements (links, mentions) support text updates
editor.updateElement({
  blockId: 'paragraph-1',
  type: 'link',
  props: { url: 'https://example.com' },
  text: 'New link text', // Only works for inline elements
});

// Block elements don't support text option
editor.updateElement({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  props: { isExpanded: true },
  // text option is ignored for block elements
});

6. Use Element Entry for Path Information

// When you need both element and its path
const entry = editor.getElementEntry({
  blockId: 'accordion-1',
  type: 'accordion-list-item',
  path: [0, 1],
});

if (entry) {
  const [element, path] = entry;
  // Use path for subsequent operations
  editor.updateElement({
    blockId: 'accordion-1',
    type: 'accordion-list-item',
    props: { isExpanded: true },
    path, // Reuse the path
  });
}

Migration from Old API

If you’re migrating from the old Elements namespace API:
// ❌ Old API (namespace with separate parameters)
Elements.updateElement(
  editor,
  blockId,
  {
    type: 'accordion-list-item',
    props: { isExpanded: true },
  },
  { path: itemPath },
);

// ✅ New API (options object on editor instance)
editor.updateElement({
  blockId,
  type: 'accordion-list-item',
  props: { isExpanded: true },
  path: itemPath,
});

Key Changes

  1. Unified Options Object: All parameters are now in a single options object
  2. Editor Instance Methods: Methods are available directly on the editor instance
  3. Consistent Naming: All methods follow the same naming pattern
  4. Better TypeScript Support: Improved type inference and autocomplete

Method Reference

CRUD Operations

MethodDescriptionReturns
insertElementCreate and insert a new elementvoid
updateElementModify element properties and/or textvoid
deleteElementRemove or unwrap an elementvoid

Retrieval Methods

MethodDescriptionReturns
getElementGet a single elementSlateElement | null
getElementsGet multiple elementsSlateElement[]
getElementEntryGet element with its path[SlateElement, Path] | null
getElementPathGet element’s Slate pathPath | null
getParentElementPathGet parent element’s pathPath | null
getElementChildrenGet element’s childrenSlateElement['children'] | null
isElementEmptyCheck if element has no text contentboolean

Next Steps