Reference

Rich Editor Reference

11 min read

The CMS Rich Editor is an enhanced version of Filament's `RichEditor` component, designed to make managing long structured pages with many blocks fast and di...

CMS Rich Editor Reference

Features

Block insertion

  • Search: Multi-word AND filtering across name, description, ID, and keywords
  • Result ranking: Matches sort by quality (exact label > startsWith > contains > id > keywords) within each category
  • Recently used: Last 5 inserted blocks pin to the top of the picker (per browser, persisted in localStorage)
  • Slash commands: Type / anywhere in the editor for a Notion-style floating block picker; arrow keys + Enter to insert
  • Categories: Blocks organized into collapsible groups
  • Icons: Each block displays its icon

Editor surface

  • Sticky side panel: Block panel docks to viewport on long pages (≥1024px); offset configurable via --tallcms-editor-sticky-offset for hosts with their own sticky topbar
  • Outline tab: Live, drag-reorderable list of every custom block in the document; click an entry to scroll-to, drag the handle to reorder
  • Per-block hover chrome: Floating mini-toolbar on every custom block — drag handle, ↑, ↓, duplicate, collapse — fades in on hover/focus, calm at rest
  • Per-block collapse: Fold any block's preview into a one-line summary card (header and chrome stay clickable)

Media + integration

  • Media Library Browser: Insert existing images without re-uploading
  • Auto Media Saving: Attached files are saved to the Media Library
  • Dark Mode: Full support via Filament-native styling
  • Backwards Compatible: Works with existing blocks and plugin blocks

Editor Surface

Sticky panel

The block panel stays docked to the viewport at min-width: 1024px, so long documents don't push the picker off-screen. The chosen offset can be customized:

.fi-fo-rich-editor {
    --tallcms-editor-sticky-offset: 4rem;  /* dock below your topbar */
}

Standalone sets this default in resources/css/filament/admin/theme.css. Plugin-mode hosts set it themselves if they have a sticky topbar.

Side-panel tabs

The side panel exposes two modes:

TabUse
BlocksThe picker (search, recently used, categories)
OutlineOrdered list of every customBlock in the document

The Outline tab shows each block's icon, an extracted heading title (when the block's config has a title / heading / headline / heading_text / name field), and the block's type label. Click an entry to scroll the editor to that block; drag the handle to reorder.

Per-block chrome

Hovering any custom block in the editor surfaces five buttons in the block header (in addition to Filament's existing edit / delete):

ButtonAction
Drag handleVisual affordance for native drag-to-reorder (small predictable hit target)
Move block up among customBlock siblings
Move block down among customBlock siblings
DuplicateInsert exact clone immediately after
CollapseToggle preview visibility — folds tall blocks into one-line summaries

All reorder + duplicate operations transact in a single TipTap step, so undo (⌘Z) reverses them cleanly.

Slash commands

Typing / after whitespace or at line start opens a floating block picker positioned at the cursor. As you type, results filter against the same searchable field used by the side panel. Arrow keys navigate, Enter or Tab inserts (the /query text is removed in the same transaction), Escape or click-away dismisses without inserting.

A default placeholder hints at the trigger on a fresh editor: "Type / for blocks, or use the side panel". Override per field via ->placeholder().


Block Categories

TallCMS includes 16 custom blocks in 5 categories:

CategoryBlocksIcon
ContentHero, Content, CTA, Features, Pricing, Dividerheroicon-o-document-text
MediaMedia Gallery, Document List, Parallaxheroicon-o-photo
Social ProofTestimonials, Team, Logos, Statsheroicon-o-star
DynamicPosts, FAQ, Timelineheroicon-o-newspaper
FormsContact Formheroicon-o-envelope

Content Blocks

BlockDescriptionKeywords
HeroBlockFull-width hero section with backgroundbanner, header, landing
ContentBlockRich text content with titlearticle, text, prose
CallToActionBlockPromotional section with buttonscta, button, action
FeaturesBlockFeature grid with iconsfeatures, benefits, list
PricingBlockPricing table with plansplans, pricing, tiers
DividerBlockDecorative spacing elementseparator, spacing, line

Media Blocks

BlockDescriptionKeywords
ImageGalleryBlockGallery with lightbox supportimages, photos, gallery
ParallaxBlockParallax scrolling sectionscroll, background, effect

Social Proof Blocks

BlockDescriptionKeywords
TestimonialsBlockCustomer testimonialsreviews, quotes, customers
TeamBlockTeam member profilesteam, staff, members
LogosBlockClient/partner logosclients, partners, brands
StatsBlockKey metrics displaynumbers, metrics, statistics

Dynamic Blocks

BlockDescriptionKeywords
PostsBlockBlog post listingblog, articles, posts
FaqBlockFAQ accordionfaq, questions, answers
TimelineBlockChronological eventshistory, events, chronology

Form Blocks

BlockDescriptionKeywords
ContactFormBlockContact formcontact, email, form

Adding Metadata to Custom Blocks

Use the HasBlockMetadata trait:

<?php

namespace App\Filament\Blocks;

use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
use TallCms\Cms\Filament\Blocks\Concerns\HasBlockMetadata;

class MyCustomBlock extends RichContentCustomBlock
{
    use HasBlockMetadata;

    public static function getId(): string
    {
        return 'my-custom-block';
    }

    public static function getLabel(): string
    {
        return 'My Custom Block';
    }

    public static function getCategory(): string
    {
        return 'content';
    }

    public static function getIcon(): string
    {
        return 'heroicon-o-sparkles';
    }

    public static function getDescription(): string
    {
        return 'A custom block that does something amazing';
    }

    public static function getKeywords(): array
    {
        return ['custom', 'amazing', 'special'];
    }

    public static function getSortPriority(): int
    {
        return 25;  // Lower = appears first
    }
}

Trait Methods

MethodReturn TypeDefaultDescription
getCategory()string'content'Category key
getIcon()string'heroicon-o-cube'Heroicon name
getDescription()string''Brief description
getKeywords()array[]Search terms
getSortPriority()int50Sort order

Available Categories

KeyLabelUse For
contentContentText, headings, CTAs, pricing
mediaMediaImages, videos, galleries
social-proofSocial ProofTestimonials, team, logos
dynamicDynamicPosts, FAQ, data-driven
formsFormsContact forms, inputs
otherOtherUncategorized blocks

Plugin Blocks

Plugin blocks are automatically discovered. Without HasBlockMetadata:

  • Appear in "Other" category
  • Use default cube icon
  • Searchable by label and ID

Adding a Custom Category

Edit BlockCategoryRegistry.php:

public static function getCategories(): array
{
    return [
        // ... existing categories
        'ecommerce' => [
            'label' => 'E-Commerce',
            'icon' => 'heroicon-o-shopping-cart',
            'order' => 45,
        ],
    ];
}

Media Library Integration

The editor integrates with the Media Library via two features:

Insert from Media Library

Click the photo icon in the toolbar to open a searchable browser of existing images. Select an image, optionally edit the alt text, and insert it directly into the editor.

  • Images are inserted with a reference to the Media Library record ID
  • Alt text pre-fills from the media record but can be overridden
  • The browser shows the 100 most recent images with client-side search

File Attachments Saved to Media Library

When you attach a file using the paperclip icon, the file is automatically saved as a Media Library record:

  • Alt text is inferred from the filename (e.g., team-photo.jpg → "Team Photo")
  • Image optimization runs in the background
  • The image node stores the media record ID, so URLs resolve from the Media Library at render time

Architecture

The integration is implemented as a Filament RichContentPlugin:

src/Filament/Forms/Components/
├── Actions/
│   └── InsertMediaAction.php         # Modal action for browsing media
├── Plugins/
│   └── MediaLibraryPlugin.php        # Registers toolbar button and action
├── CmsRichEditor.php                 # Registers plugin, adds toolbar button
└── MediaLibraryFileAttachmentProvider.php  # Saves attachments as media records
ClassRole
MediaLibraryPluginImplements RichContentPlugin, provides the insertMedia tool and action
InsertMediaActionFilament Action with modal schema, queries TallcmsMedia, runs insertContent editor command
MediaLibraryFileAttachmentProviderSaves uploaded files as TallcmsMedia records, resolves media IDs to URLs

Using CmsRichEditor

The CmsRichEditor is automatically used in page and post forms. For custom resources:

use TallCms\Cms\Filament\Forms\Components\CmsRichEditor;
use TallCms\Cms\Services\CustomBlockDiscoveryService;

CmsRichEditor::make('content')
    ->customBlocks(CustomBlockDiscoveryService::getBlocksArray())
    ->activePanel('customBlocks')

Architecture

packages/tallcms/cms/
├── src/
│   ├── Filament/
│   │   ├── Blocks/
│   │   │   └── Concerns/
│   │   │       └── HasBlockMetadata.php
│   │   └── Forms/
│   │       └── Components/
│   │           ├── Actions/
│   │           │   └── InsertMediaAction.php
│   │           ├── Plugins/
│   │           │   ├── BlockChromePlugin.php
│   │           │   └── MediaLibraryPlugin.php
│   │           ├── CmsRichEditor.php
│   │           └── MediaLibraryFileAttachmentProvider.php
│   └── Services/
│       ├── BlockCategoryRegistry.php
│       └── CustomBlockDiscoveryService.php
└── resources/
    ├── css/
    │   └── admin.css                   # editor surface styles (sticky, chrome, outline, slash)
    ├── js/
    │   └── block-chrome.js             # TipTap extension source
    ├── dist/
    │   ├── tallcms-admin.css           # built CSS shipped to plugin-mode installs
    │   └── block-chrome.js             # built TipTap extension (loaded on-request)
    └── views/
        └── filament/
            └── forms/
                └── components/
                    ├── cms-rich-editor.blade.php
                    └── media-library-picker.blade.php

Key Classes

CmsRichEditor

Extends Filament's RichEditor:

  • getGroupedBlocks() — Returns blocks by category
  • getBlockCategories() — Returns category definitions
  • getDefaultToolbarButtons() — Adds insertMedia after attachFiles
  • isFilamentCompatible() — Version check for fallback
  • Registers MediaLibraryPlugin and BlockChromePlugin via setUp()
  • Sets the default placeholder hint for slash commands

BlockChromePlugin

Filament RichContentPlugin that ships the editor-surface TipTap extension via getTipTapJsExtensions(). The bundled JS (loaded on-request via Js::make()->loadedOnRequest()) registers three ProseMirror plugins inside one TipTap extension:

PluginRole
BlockChromeViewInjects per-block hover chrome (drag handle, ↑, ↓, duplicate, collapse) into each customBlock's header
OutlineSyncViewEmits cms-block-outline-changed events on doc change so the Outline tab can render; listens for cms-block-action for outline-driven scroll/move requests
SlashCommandViewDetects / triggers, renders the floating slash menu, dispatches cms-slash-insert on selection

Three TipTap commands are registered on top-level customBlock nodes:

CommandSignaturePurpose
moveCustomBlockUp(pos)Swap with previous top-level sibling
moveCustomBlockDown(pos)Swap with next top-level sibling
moveCustomBlockTo(fromPos, toIndex)Move to a specific slot among customBlock siblings (used by Outline drag-reorder)
duplicateCustomBlock(pos)Insert exact attrs+content clone immediately after

All commands run in single transactions for clean undo, validate the node type at the position, and no-op cleanly at boundaries (top, bottom, non-customBlock positions).

BlockCategoryRegistry

Static registry:

  • Category keys, labels, icons, sort order
  • Fallback icon constant

CustomBlockDiscoveryService

Enhanced discovery:

  • getBlocksWithMetadata() - Full metadata
  • getBlocksGroupedByCategory() - Grouped output
  • Icon validation
  • Precomputed search strings (used by both the picker and slash menu)

Filament Compatibility

Requires Filament v5.x. Automatic version detection:

  • Filament 5.x: Full enhanced panel
  • Other versions: Standard Filament block panel

Next Steps

Comments

No comments yet. Be the first to share your thoughts!

Choose Theme