| description | Build new shadcn-style components for Plate's registry and editor surfaces. Use when authoring or refactoring registry UI/components, deciding what belongs in packages vs app-local component files, creating base/live kits and registry wiring, or applying React Compiler, Effects, accessibility, polymorphism, data-slot, data-state, and composable-component rules to Plate UI work. |
| name | plate-ui |
| metadata | {"skiller":{"source":".agents/rules/plate-ui.mdc"}} |
Plate UI
Repo-specific companion to the shadcn skill.
Use the shadcn skill for CLI, upstream docs, and generic shadcn/ui rules.
Use this skill for Plate-specific component authorship: open-code preservation,
package extraction boundaries, base/live kit split, cross-platform layering,
and registry wiring.
Repo Surfaces
apps/www/src/registry/ui — live component and node renderers
apps/www/src/registry/components/editor/plugins — base/live kit wiring
apps/www/src/registry/registry-*.ts — registry metadata and dependencies
packages/* — durable transforms, queries, controllers, and public hooks
Principles
- Preserve open code. A shadcn-derived component should still look like source code a user can own, read, diff, and tweak.
- Extract only durable boundaries. Package code should own semantics, not JSX avoidance.
- Design below JSX. Cross-platform reuse belongs in command/state contracts, controllers, queries, and transforms — not in package-owned shadcn composition.
- Keep UI composition local until proven otherwise. Popovers, labels, and layout belong in the component unless multiple surfaces need the same contract.
- Registry wiring is part of authorship. A component is not done until kits, examples, and style deps are coherent.
- React floor is 19.2+. Do not add backward-compat code for React 18-era limitations or patterns.
Critical Rules
packages/*/src/lib owns semantic core: transforms, queries, schemas, serialization, controllers, command/state contracts.
packages/*/src/react is a thin adapter layer only.
- Future native layers should consume the same conceptual contracts, not React-specific convenience hooks.
- If a package React hook mainly returns renderer-specific UI props/state, treat it as migration debt, not precedent.
Ownership & Extraction → ownership.md
- Extract package code for transforms, queries, serialization, stable controllers, and public hooks reused across surfaces.
- Keep one-off shadcn composition, labels, popover state, and local visual treatment in the app component.
- Never create a package hook just to hide JSX, avoid typing work, or move logic used by one component only.
- If extraction makes the component harder to compare with upstream shadcn/open code, keep it local.
- For sibling live/static registry renderers, duplicate presentation lookup data
and tiny label helpers in each renderer instead of creating a third shared
registry file. Extract only when the shared code owns real behavior beyond
labels, menu data, or copy.
- Node renderers use node-context hooks like
useElement() or usePath() when they are in element context.
- Prefer direct
editor.getApi(plugin) / editor.getTransforms(plugin) or useEditorPlugin(plugin) over local wrapper helpers.
- If a node renderer forwards to
PlateElement or SlateElement, keep the full incoming props object intact. Read from props, but do not destructure away editor, element, or other required fields and then spread only a partial object into the renderer.
- Keep helpers inline when used once.
- Split static/base and live kits cleanly.
- Target React
>=19.2. Do not preserve React 18 compatibility patterns unless the user explicitly asks.
- Effects are escape hatches, not state calculators.
- Derive during render unless synchronizing with a real external system.
- Put interaction logic in event handlers, not in effects watching state.
- Do not subscribe to fast-changing editor state unless the rendered output truly depends on it.
- Do not define nested components inside components.
- Update
registry-kits.ts, registry-ui.ts, and registry-examples.ts together.
- Add explicit
registryDependencies for every shared UI/style dependency.
- If a component depends on shared CSS vars like highlight tokens, add the style registry dep.
- Examples should depend on kits plus any extra styles/components they introduce.
- Keep
asChild, data-slot, data-state, variants, and file shape recognizable.
- Prefer one readable file with local subparts over scattering tiny hooks.
- Review custom code like an upstream diff: would this still feel like open source, or like framework sludge?
Major-Release Law
For the future redesign, use this as the default:
Package React hooks that mainly return renderer-specific UI props/state
should be deprecated and moved app-local. Package layers keep cross-platform
semantic/view-model contracts only.
If an existing hook breaks this law, do not copy it into new work just because
it already exists.
Extraction Test
Extract to a package only if at least one is true:
- The code owns document semantics, serialization, transforms, or navigation contracts.
- Multiple UI surfaces or platforms need the same behavior contract.
- The code is a stable controller/hook whose output is not tied to one shadcn component's markup.
- The same logic would otherwise be duplicated across packages or adapters.
- A future native consumer could plausibly use the same conceptual contract.
Keep it local if any of these are true:
- The code only serves one component.
- The return shape is mostly labels, JSX wiring, class decisions, or popover/menu state.
- The main reason to extract is "this file feels long" or "types are annoying".
- The extraction would hide open-code structure from users.
- The abstraction only makes sense in React/web and has no plausible native sibling.
Key Patterns
const { align, focused, readOnly, selected } = useMediaState();
return (
<MediaToolbar plugin={ImagePlugin}>
<PlateElement {...props}>...</PlateElement>
</MediaToolbar>
);
const api = editor.getApi(CommentPlugin).comment;
const tf = editor.getTransforms(CommentPlugin).comment;
const canToggleBold = bridgeState.canToggleBold;
const onPress = () => editor.toggleBold();
return <Button disabled={!canToggleBold}>Bold</Button>;
export const BaseFootnoteKit = [
BaseFootnoteReferencePlugin.withComponent(FootnoteReferenceElementStatic),
BaseFootnoteDefinitionPlugin.withComponent(FootnoteDefinitionElementStatic),
];
export const FootnoteKit = [
FootnoteInputPlugin.withComponent(FootnoteInputElement),
FootnoteReferencePlugin.withComponent(FootnoteReferenceElement),
FootnoteDefinitionPlugin.withComponent(FootnoteDefinitionElement),
];
const state = useSingleComponentOnlyState();
return <Popover open={state.open}>...</Popover>;
const {
dialogTitle,
menuItems,
onOpenChange,
popoverOpen,
} = useToolbarMenuState();
Workflow
- Start with the
shadcn skill. Run the normal shadcn docs/search workflow first.
- Search Plate for the closest analog in
apps/www/src/registry/ui, apps/www/src/registry/components/editor/plugins, and the relevant packages/*.
- Decide ownership with the extraction test before writing code.
- Decide the three layers before coding:
- semantic core
- platform adapter
- local open-code UI
- Apply the React checks before writing state/effects:
- can this be derived during render?
- should this stay in an event handler?
- am I subscribing to more editor state than the UI actually renders?
- Build the component as open code first.
- Extract only the boundaries that survive the test.
- Wire base/live kits and registry deps.
- If package exports changed, run
pnpm brl.
- Verify the smallest honest surface:
- component spec for UI-only changes
- package build/typecheck when package code changed
- browser verification when the surface is interactive
Audit References
Comprehensive References
Load these only when the task needs the detail:
.agents/rules/plate-ui/references/components.md — comprehensive component architecture reference: accessibility, asChild, composition, data attributes, artifact taxonomy, polymorphism, controlled/uncontrolled state, and component typing.
.agents/rules/plate-ui/references/react.md — comprehensive React reference: React Compiler, manual memoization escape hatches, Effects, useEffectEvent, ref access, derived state, Tailwind v4 syntax, data attributes, and UI constraints.
Detailed References