Skip to content

feat(editor): add built-in SlashCommand extension#3050

Merged
joaopcm merged 12 commits intofeat/ai-oss-editorfrom
feature/product-1567-start-making-examples-for-us-to-experiment-while-working-on
Mar 12, 2026
Merged

feat(editor): add built-in SlashCommand extension#3050
joaopcm merged 12 commits intofeat/ai-oss-editorfrom
feature/product-1567-start-making-examples-for-us-to-experiment-while-working-on

Conversation

@joaopcm
Copy link
Member

@joaopcm joaopcm commented Mar 11, 2026

Summary

  • Adds a generic, extensible SlashCommand extension to @react-email/editor
  • 14 built-in email commands (text, headings, lists, quote, code block, button, divider, section, columns)
  • 7-tier search scoring algorithm for command filtering
  • Keyboard navigation (ArrowUp/Down, Enter, Escape) with category grouping
  • createSlashCommand() factory for custom command sets — designed so the dashboard can consume this and add its own commands (image upload, YouTube, variables, etc.)
  • Tippy.js dropdown UI with minimal CSS (re-slash-command- prefix) and dark mode support

Test plan

  • 275 tests pass (19 new tests for search scoring + filtering)
  • Editor package builds successfully
  • CSS gets copied to dist correctly
  • Biome lint passes
  • Manual verification: slash command dropdown appears on / keypress in an editor instance
  • Manual verification: keyboard navigation works correctly
  • Manual verification: commands execute properly

Summary by cubic

Adds a built-in, extensible SlashCommand to @react-email/editor for fast block insertion via a searchable, keyboard-first menu. Fixes scroll positioning for reliable keyboard navigation in custom dropdowns and bumps @react-email/editor to 0.0.0-experimental.15, meeting PRODUCT‑1567.

  • New Features

    • 14 built-in commands: text, H1–H3, bullet/numbered lists, quote, code, button, divider, section, 2/3/4 columns.
    • 7‑tier search scoring with filtering; grouped by category; ArrowUp/Down, Enter, Escape.
    • createSlashCommand() factory + ready‑to‑use SlashCommand; default filter hides column layouts at max column depth.
    • Custom dropdown UI via createSlashCommand({ component }); exports CommandList, CommandListProps, CommandListRef, CommandListComponent.
    • Dropdown via tippy.js; layout‑only styles at @react-email/editor/styles/slash-command.css using data-re- attributes; command titles in sentence case; uses React 19 ref prop; trims queries; guards empty lists.
    • Default theme now imports slash-command.css.
  • Dependencies

    • Added @tiptap/suggestion.
    • Added tippy.js.

Written for commit c61aaca. Summary will update on new commits.

Extensible slash command with 14 built-in email commands, 7-tier search
scoring, keyboard navigation, and Tippy.js dropdown UI.

Exports createSlashCommand() factory for custom command sets + a
pre-configured SlashCommand for quick setup.
@vercel
Copy link

vercel bot commented Mar 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-email Ready Ready Preview, Comment Mar 11, 2026 5:13pm
react-email-demo Ready Ready Preview, Comment Mar 11, 2026 5:13pm

Request Review

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 14 files

Confidence score: 3/5

  • There is some user-facing risk around slash command navigation: updateScrollView in packages/editor/src/ui/slash-command/utils.ts can mis-scroll when the offset parent isn’t the scroll container.
  • Search matching can fail with leading/trailing spaces because packages/editor/src/ui/slash-command/search.ts scores the untrimmed query.
  • Keyboard navigation can break when there are no items because modulo by zero yields NaN in packages/editor/src/ui/slash-command/command-list.tsx, so this isn’t fully safe to merge without tweaks.
  • Pay close attention to packages/editor/src/ui/slash-command/utils.ts, packages/editor/src/ui/slash-command/search.ts, packages/editor/src/ui/slash-command/command-list.tsx - scroll behavior, query trimming, and empty-state navigation handling.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/editor/src/ui/slash-command/search.ts">

<violation number="1" location="packages/editor/src/ui/slash-command/search.ts:34">
P2: Use the trimmed query for scoring; otherwise leading/trailing spaces can make valid commands fail to match.</violation>
</file>

<file name="packages/editor/src/ui/slash-command/command-list.tsx">

<violation number="1" location="packages/editor/src/ui/slash-command/command-list.tsx:110">
P2: Guard keyboard navigation when there are no items; modulo by zero sets `selectedIndex` to `NaN`.</violation>
</file>

<file name="packages/editor/src/ui/slash-command/utils.ts">

<violation number="1" location="packages/editor/src/ui/slash-command/utils.ts:21">
P2: `updateScrollView` uses `item.offsetTop` with `container.scrollTop`, which can mis-scroll when the item’s `offsetParent` is not the scroll container. Use viewport-relative rects (or normalize to container coordinates) before adjusting scroll.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

⚠️ No Changeset found

Latest commit: 063e922

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@joaopcm joaopcm self-assigned this Mar 11, 2026
joaopcm added 2 commits March 11, 2026 13:48
Adds a `component` option to `createSlashCommand()` so consumers can
provide their own dropdown UI while reusing the extension plumbing
(suggestion plugin, Tippy positioning, keyboard delegation).

Exports CommandList, CommandListProps, CommandListRef, and
CommandListComponent for consumers to build on or replace.
…only

Aligns with existing bubble menu CSS pattern: layout and positioning
only, no colors/shadows/borders. Consumers own the visual layer.
joaopcm added 2 commits March 11, 2026 14:05
offsetTop is relative to offsetParent which may not be the scroll
container in custom component layouts. Viewport-relative rects work
regardless of DOM nesting.
@joaopcm joaopcm requested a review from gabrielmfern March 11, 2026 17:09
@socket-security
Copy link

socket-security bot commented Mar 11, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​tiptap/​suggestion@​3.20.11001007799100

View full report

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 11, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@react-email/editor@3050

commit: db24330

onClick={onSelect}
type="button"
>
<Icon size={20} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be coming in as a React.ReactNode? Maybe children?

} from 'lucide-react';
import type { SlashCommandItem } from './types';

export const TEXT: SlashCommandItem = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't have to be now, but I wonder if there's a way to have extensions plug into the slash command and show up as an option at the end


if (!props.clientRect) return;

popup = tippy('body', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't have to be now either, but could this popover be defined through React components in the React tree and then still be handles by the extension somehow?

maybe the component adds in the extension to the editor when mounting (I think this is possible)

@joaopcm
Copy link
Member Author

joaopcm commented Mar 12, 2026

@gabrielmfern I've moved all of your comments to the Linear board and we'll tackle those from there as follow-up PRs! Thanks for the awesome review

@joaopcm joaopcm merged commit 9720697 into feat/ai-oss-editor Mar 12, 2026
15 checks passed
@joaopcm joaopcm deleted the feature/product-1567-start-making-examples-for-us-to-experiment-while-working-on branch March 12, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants