Date adds inline void elements that display a date label inside text. One required value preserves either a canonical YYYY-MM-DD date or authored text such as sometime next week.
DateKit installs DatePlugin with the registry DateElement.
'use client';
import {
formatDateValue,
getDateDisplayLabel,
parseCanonicalDateValue,
} from '@platejs/date';
import { DatePlugin } from '@platejs/date/react';
import {
type PlateElementProps,
PlateElement,
useEditorReadOnly,
} from 'platejs/react';
import * as React from 'react';
import { Calendar } from '@/components/ui/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
export function DateElement(props: PlateElementProps<typeof DatePlugin>) {
const { editor, element } = props;
const readOnly = useEditorReadOnly();
const trigger = (
<button
className={cn(
'w-fit cursor-pointer rounded-sm bg-muted px-1 text-muted-foreground',
inlineSuggestionVariants()
)}
contentEditable={false}
draggable
type="button"
>
{getDateDisplayLabel(element.value)}
</button>
);
return (
<PlateElement
{...props}
className="inline-block"
attributes={{
...props.attributes,
contentEditable: false,
}}
>
{readOnly ? (
trigger
) : (
<Popover>
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
selected={parseCanonicalDateValue(element.value)}
onSelect={(date) => {
if (!date) return;
editor.update.nodes.set(
{ value: formatDateValue(date) },
{ at: element }
);
}}
mode="single"
initialFocus
/>
</PopoverContent>
</Popover>
)}
{props.children}
</PlateElement>
);
}
export const DateKit = [DatePlugin.configure({ component: DateElement })];'use client';
import {
formatDateValue,
getDateDisplayLabel,
parseCanonicalDateValue,
} from '@platejs/date';
import { DatePlugin } from '@platejs/date/react';
import {
type PlateElementProps,
PlateElement,
useEditorReadOnly,
} from 'platejs/react';
import * as React from 'react';
import { Calendar } from '@/components/ui/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { createPlateEditor } from 'platejs/react';
import { DateKit } from '@/components/editor/date';
export const editor = createPlateEditor({
plugins: DateKit,
});import { createPlateEditor } from 'platejs/react';
import { DateKit } from '@/components/editor/date';
export const editor = createPlateEditor({
plugins: DateKit,
});date owns the inline wrapper, display label, popover, calendar picker, and static element.
'use client';
import {
formatDateValue,
getDateDisplayLabel,
parseCanonicalDateValue,
} from '@platejs/date';
import { DatePlugin } from '@platejs/date/react';
import {
type PlateElementProps,
PlateElement,
useEditorReadOnly,
} from 'platejs/react';
import * as React from 'react';
import { Calendar } from '@/components/ui/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from
The registry insert toolbar maps PLUGINS.date to the plugin-owned update.
import { BaseDatePlugin } from '@platejs/date';
import { PLUGINS } from 'platejs';
export const insertInlineMap = {
[PLUGINS.date]: (editor) =>
editor.plugin(BaseDatePlugin).update.insert({}, { select: true }),
};import { BaseDatePlugin } from '@platejs/date';
import
| Layer | Owner | What It Does |
|---|---|---|
@platejs/date | Package | Exports BaseDatePlugin and date value helpers. |
@platejs/date/react | Package | Exports DatePlugin. |
date | Registry | Adds DatePlugin.configure({ component: DateElement }). |
date-static | Registry | Adds BaseDatePlugin.configure({ component: DateElementStatic }). |
date | Registry UI | Renders the editable popover/calendar element and static element. |
@platejs/markdown | Package | Converts date MDX tags to the package-owned value. |
BaseDatePlugin is inline and void. The text child exists only to satisfy Plite's element shape.
Use the React plugin when the editor renders the calendar popover.
import { DatePlugin } from '@platejs/date/react';
import { createPlateEditor } from 'platejs/react';
import { DateElement } from '@/components/editor/date';
export const editor = createPlateEditor({
plugins: [DatePlugin.configure({ component: DateElement })],
});import { DatePlugin } from '@platejs/date/react';
import { createPlateEditor } from 'platejs/react';
import { DateElement } from '@/components/editor/date';
export const editor = createPlateEditor({
plugins: [DatePlugin.configure({ component: DateElement })],
});Use the base kit when rendering read-only output with platejs/static.
import { BaseDatePlugin, getDateDisplayLabel } from '@platejs/date';
import { type PliteElementProps, PliteElement } from 'platejs/static';
import * as React from 'react';
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
export function DateElementStatic(
props: PliteElementProps<typeof BaseDatePlugin>
) {
const { element } = props;
return (
<PliteElement as="span"
DatePlugin exposes date insertion through the date transaction group.
editor.update((tx) =>
tx.date.insert({ value: '2026-03-23' }, { select: true })
);editor.update((tx) =>
tx.date.insert({ value: '2026-03-23' }, { select: true })
);Date elements store one non-empty authored string. Parsing is derived from that value rather than persisted as a second field.
const value = [
{
children: [
{ text: 'Due ' },
{
children: [{ text: '' }],
type: 'date',
value: '2026-03-23',
},
{ text: '.' },
],
type: 'paragraph',
},
];const value = [
{
children: [
{ text: 'Due ' },
{
children: [{ text: '' }],
type: 'date',
value: '2026-03-23',
},
{ text: '.' },
],
type: 'paragraph',
},
];| Field | Type | Notes |
|---|---|---|
type | 'date' | Persisted element type owned by BaseDatePlugin. |
children | [{ text: '' }] | Required Plite child for the inline void element. |
value | string | Required canonical date or authored date text. |
normalizeDateValue returns the one persisted string.
| Input | Stored Value |
|---|---|
Date object | formatDateValue(value) when the object is valid. |
YYYY-MM-DD | The trimmed string. |
| Invalid canonical string | The trimmed authored string. |
Mon Mar 23 2026 | '2026-03-23' when JavaScript can parse it. |
| Blank string | undefined; no Date node is constructed. |
| Other text | The trimmed authored string. |
getDateDisplayLabel(value) returns Today, Yesterday, Tomorrow, a localized long date, or the authored string.
The registry element is display-only while read-only. In editable mode, clicking the inline label opens a calendar popover.
| State | Behavior |
|---|---|
canonical value | The trigger shows a relative or localized date label. |
other value | The trigger shows the authored string. |
| calendar selection | The node is set to { value: formatDateValue(date) }. |
The registry element uses contentEditable={false} on the inline wrapper, so users edit the date through the calendar instead of typing inside the void node.
Canonical values serialize as a self-closing date tag with a value attribute.
Date: <date value="2026-03-23" />Date: <date value="2026-03-23" />Other authored values serialize as child text.
Date: <date>sometime next week</date>Date: <date>sometime next week</date>The deserializer also accepts child text such as <date>Mon Mar 23 2026</date> and normalizes it to value: '2026-03-23' when the date is safe to parse.
| API | Package | Use |
|---|---|---|
BaseDatePlugin | @platejs/date | Headless inline void date plugin. |
DatePlugin | @platejs/date/react | React date plugin. |
editor.plugin(BaseDatePlugin).update.insert(input?, options?) | BaseDatePlugin update | Inserts a date with separate date input and node placement options. |
normalizeDateValue(value) | @platejs/date | Returns the canonical or authored string, or undefined for empty/invalid input. |
formatDateValue(date) | @platejs/date | Formats a Date object as YYYY-MM-DD. |
parseCanonicalDateValue(value) | @platejs/date | Parses only valid canonical date strings. |
getDateDisplayLabel(value, options?) | @platejs/date | Builds the visible date label. |
DateElement | @platejs/date | Element shape with one required value. |
'use client';
import {
formatDateValue,
getDateDisplayLabel,
parseCanonicalDateValue,
} from '@platejs/date';
import { DatePlugin } from '@platejs/date/react';
import {
type PlateElementProps,
PlateElement,
useEditorReadOnly,
} from 'platejs/react';
import * as React from 'react';
import { Calendar } from '@/components/ui/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
export function DateElement(props: PlateElementProps<typeof DatePlugin>) {
const { editor, element } = props;
const readOnly = useEditorReadOnly();
const trigger = (
<button
className={cn(
'w-fit cursor-pointer rounded-sm bg-muted px-1 text-muted-foreground',
inlineSuggestionVariants()
)}
contentEditable={false}
draggable
type="button"
>
{getDateDisplayLabel(element.value)}
</button>
);
return (
<PlateElement
{...props}
className="inline-block"
attributes={{
...props.attributes,
contentEditable: false,
}}
>
{readOnly ? (
trigger
) : (
<Popover>
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
selected={parseCanonicalDateValue(element.value)}
onSelect={(date) => {
if (!date) return;
editor.update.nodes.set(
{ value: formatDateValue(date) },
{ at: element }
);
}}
mode="single"
initialFocus
/>
</PopoverContent>
</Popover>
)}
{props.children}
</PlateElement>
);
}
export const DateKit = [DatePlugin.configure({ component: DateElement })];import { BaseDatePlugin, getDateDisplayLabel } from '@platejs/date';
import { type PliteElementProps, PliteElement } from 'platejs/static';
import * as React from 'react';
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/inline-suggestion';
export function DateElementStatic(
props: PliteElementProps<typeof BaseDatePlugin>
) {
const { element } = props;
return (
<PliteElement as="span" className="inline-block" {...props}>
<span
className={cn(
'w-fit rounded-sm bg-muted px-1 text-muted-foreground',
inlineSuggestionVariants()
)}
>
{getDateDisplayLabel(element.value)}
</span>
{props.children}
</PliteElement>
);
}
export const BaseDateKit = [
BaseDatePlugin.configure({ component: DateElementStatic }),
];