Date

PreviousNext

Inline void date elements with canonical and raw date storage.

Date adds inline void elements that display a date label inside text. Canonical dates are stored as YYYY-MM-DD; non-normalizable input is kept as rawDate. This page covers kit setup, insertion, value shape, picker behavior, Markdown serialization, and the small query API.

Loading…

Features

  • Inline void date element.
  • Bound editor.tf.insert.date transform.
  • Direct insertDate(editor, options) helper.
  • Canonical date storage with rawDate fallback.
  • Calendar editing in the registry UI.
  • Static renderer for read-only output.
  • Markdown round-trip through <date value="YYYY-MM-DD" /> and child-text date tags.

Fast Path

Add The Kit

DateKit installs DatePlugin with the registry DateElement.

'use client';
 
import { DatePlugin } from '@platejs/date/react';
 
import { DateElement } from '@/components/ui/date-node';
 
export const DateKit = [DatePlugin.withComponent(DateElement)];
'use client';
 
import { DatePlugin } from '@platejs/date/react';
 
import { DateElement } from '@/components/ui/date-node';
 
export const DateKit = [DatePlugin.withComponent(DateElement)];
import { createPlateEditor } from 'platejs/react';
 
import { DateKit } from '@/components/editor/plugins/date-kit';
 
export const editor = createPlateEditor({
  plugins: DateKit,
});
import { createPlateEditor } from 'platejs/react';
 
import { DateKit } from '@/components/editor/plugins/date-kit';
 
export const editor = createPlateEditor({
  plugins: DateKit,
});

Render The Element

date-node owns the inline wrapper, display label, popover, calendar picker, and static element.

'use client';
 
import * as React from 'react';
 
import {
  formatDateValue,
  getDateDisplayLabel,
  parseCanonicalDateValue,
} from '@platejs/date';
import type { TDateElement } from 'platejs';
import type { PlateElementProps } from 'platejs/react';
 
import { PlateElement, useReadOnly } from 'platejs/react';
 
import { Calendar } from '@/components/ui/calendar';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/suggestion';
 
export function DateElement(props: PlateElementProps<TDateElement>) {
  const { editor, element } = props;
  const readOnly = useReadOnly();
 
  const trigger = (
    <span
      className={cn(
        'w-fit cursor-pointer rounded-sm bg-muted px-1 text-muted-foreground',
        inlineSuggestionVariants()
      )}
      contentEditable={false}
      draggable
    >
      {element.date || element.rawDate ? (
        getDateDisplayLabel(element)
      ) : (
        <span>Pick a date</span>
      )}
    </span>
  );
 
  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.date ?? '')}
              onSelect={(date) => {
                if (!date) return;
 
                editor.tf.setNodes(
                  { date: formatDateValue(date), rawDate: undefined },
                  { at: element }
                );
              }}
              mode="single"
              initialFocus
            />
          </PopoverContent>
        </Popover>
      )}
      {props.children}
    </PlateElement>
  );
}
'use client';
 
import * as React from 'react';
 
import {
  formatDateValue,
  getDateDisplayLabel,
  parseCanonicalDateValue,
} from '@platejs/date';
import type { TDateElement } from 'platejs';
import type { PlateElementProps } from 'platejs/react';
 
import { PlateElement, useReadOnly } from 'platejs/react';
 
import { Calendar } from '@/components/ui/calendar';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { inlineSuggestionVariants } from '@/lib/suggestion';
 
export function DateElement(props: PlateElementProps<TDateElement>) {
  const { editor, element } = props;
  const readOnly = useReadOnly();
 
  const trigger = (
    <span
      className={cn(
        'w-fit cursor-pointer rounded-sm bg-muted px-1 text-muted-foreground',
        inlineSuggestionVariants()
      )}
      contentEditable={false}
      draggable
    >
      {element.date || element.rawDate ? (
        getDateDisplayLabel(element)
      ) : (
        <span>Pick a date</span>
      )}
    </span>
  );
 
  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.date ?? '')}
              onSelect={(date) => {
                if (!date) return;
 
                editor.tf.setNodes(
                  { date: formatDateValue(date), rawDate: undefined },
                  { at: element }
                );
              }}
              mode="single"
              initialFocus
            />
          </PopoverContent>
        </Popover>
      )}
      {props.children}
    </PlateElement>
  );
}

Add An Insert Action

The registry insert toolbar maps KEYS.date to insertDate(editor, { select: true }).

components/editor/transforms.ts
import { insertDate } from '@platejs/date';
import { KEYS } from 'platejs';
 
export const insertInlineMap = {
  [KEYS.date]: (editor) => insertDate(editor, { select: true }),
};
components/editor/transforms.ts
import { insertDate } from '@platejs/date';
import { KEYS } from 'platejs';
 
export const insertInlineMap = {
  [KEYS.date]: (editor) => insertDate(editor, { select: true }),
};

Ownership

LayerOwnerWhat It Does
@platejs/datePackageExports BaseDatePlugin, insertDate, date value helpers, and isPointNextToNode.
@platejs/date/reactPackageExports DatePlugin.
date-kitRegistryAdds DatePlugin.withComponent(DateElement).
date-base-kitRegistryAdds BaseDatePlugin.withComponent(DateElementStatic).
date-nodeRegistry UIRenders the editable popover/calendar element and static element.
@platejs/markdownPackageConverts date MDX tags to canonical date or fallback rawDate values.

BaseDatePlugin is inline and void. The text child exists only to satisfy Slate's element shape.

Manual Setup

Install Package

pnpm add @platejs/date
pnpm add @platejs/date

Add The Plugin

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/ui/date-node';
 
export const editor = createPlateEditor({
  plugins: [DatePlugin.withComponent(DateElement)],
});
import { DatePlugin } from '@platejs/date/react';
import { createPlateEditor } from 'platejs/react';
 
import { DateElement } from '@/components/ui/date-node';
 
export const editor = createPlateEditor({
  plugins: [DatePlugin.withComponent(DateElement)],
});

Add Static Rendering

Use the base kit when rendering read-only output with platejs/static.

import { BaseDatePlugin } from '@platejs/date';
 
import { DateElementStatic } from '@/components/ui/date-node-static';
 
export const BaseDateKit = [BaseDatePlugin.withComponent(DateElementStatic)];
import { BaseDatePlugin } from '@platejs/date';
 
import { DateElementStatic } from '@/components/ui/date-node-static';
 
export const BaseDateKit = [BaseDatePlugin.withComponent(DateElementStatic)];

Insert A Date

DatePlugin binds insertDate to editor.tf.insert.date.

editor.tf.insert.date({
  date: '2026-03-23',
  select: true,
});
editor.tf.insert.date({
  date: '2026-03-23',
  select: true,
});

Use the package helper directly when you are outside plugin-bound transform access.

import { insertDate } from '@platejs/date';
 
insertDate(editor, {
  date: '2026-03-23',
  select: true,
});
import { insertDate } from '@platejs/date';
 
insertDate(editor, {
  date: '2026-03-23',
  select: true,
});

Value Shape

Canonical date values live in date. Invalid or intentionally loose date text lives in rawDate.

const value = [
  {
    children: [
      { text: 'Due ' },
      {
        children: [{ text: '' }],
        date: '2026-03-23',
        type: 'date',
      },
      { text: '.' },
    ],
    type: 'p',
  },
];
const value = [
  {
    children: [
      { text: 'Due ' },
      {
        children: [{ text: '' }],
        date: '2026-03-23',
        type: 'date',
      },
      { text: '.' },
    ],
    type: 'p',
  },
];
FieldTypeNotes
type'date'Plugin key and node type from KEYS.date.
children[{ text: '' }]Required Slate child for the inline void element.
datestringCanonical YYYY-MM-DD value.
rawDatestringFallback for non-normalizable input.

Date Normalization

normalizeDateValue decides which field is written.

InputStored Value
Date objectdate: formatDateValue(value) when the object is valid.
YYYY-MM-DDdate when the calendar date is valid.
Invalid canonical stringrawDate.
Mon Mar 23 2026date: '2026-03-23' when JavaScript can parse it.
Blank stringno date fields.
Other textrawDate.

getDateDisplayLabel returns Today, Yesterday, Tomorrow, a localized long date, or the raw fallback string.

Picker Behavior

The registry element is display-only while read-only. In editable mode, clicking the inline label opens a calendar popover.

StateBehavior
date existsThe trigger shows getDateDisplayLabel(element).
rawDate existsThe trigger shows the raw string.
no date fieldsThe trigger shows Pick a date.
calendar selectionThe node is set to { date: formatDateValue(date), rawDate: undefined }.

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.

Markdown

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" />

Raw fallback 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 date: '2026-03-23' when the date is safe to parse.

API Reference

APIPackageUse
BaseDatePlugin@platejs/dateHeadless inline void date plugin.
DatePlugin@platejs/date/reactReact date plugin.
insertDate(editor, options)@platejs/dateInserts a date element plus a trailing text space.
editor.tf.insert.date(options)plugin-bound transformBound transform registered by BaseDatePlugin.
normalizeDateValue(value)@platejs/dateReturns { date }, { rawDate }, or an empty object.
formatDateValue(date)@platejs/dateFormats a Date object as YYYY-MM-DD.
parseCanonicalDateValue(value)@platejs/dateParses only valid canonical date strings.
getDateDisplayLabel(options)@platejs/dateBuilds the visible date label.
isPointNextToNode(editor, options)@platejs/dateChecks whether a point is adjacent to a node type. Throws when neither options.at nor editor selection exists.
TDateElementplatejsElement shape with optional date and rawDate.