HapplyUI

Components

Markdown Editor

A markdown text editor with formatting toolbar and live preview. Built-in English/French language toggle with per-language value storage — or single-language mode for simpler use cases. Uses `marked` for accurate markdown rendering.


Installation

bunx @happlyui/cli@latest add markdown-editor

Dependencies

npm packages

  • @remixicon/react
  • marked

Registry dependencies

These are automatically installed when you add this component.

  • form-field-context
  • use-form-field-binding
  • switch-toggle
  • textarea
  • tooltip
  • happly-ui-utils

Usage

import * as MarkdownEditor from "@/components/ui/markdown-editor"

// Multi-language (default) — value is { en: "", fr: "" }
<MarkdownEditor.Composed
  placeholder="Start writing..."
  onChange={(values) => console.log(values)}
/>

// Single-language — value is a plain string
<MarkdownEditor.Composed
  toggleItems={false}
  placeholder="Start writing..."
  onChange={(md) => console.log(md)}
/>

Examples

Default

Multi-language editor with built-in English/French toggle. Manages `{ en: "", fr: "" }` state internally.

<MarkdownEditor.Composed
  placeholder="Describe your ideal successor..."
/>

Single Language

No language toggle. Value is a plain markdown string.

<MarkdownEditor.Composed
  toggleItems={false}
  placeholder="Write in a single language..."
/>

Controlled Multi-Language

Parent owns the per-language values object. The component handles language switching internally.

{
  "en": "Hello **world**",
  "fr": "Bonjour **le monde**"
}
const [values, setValues] = useState<LocalizedValue>({
  en: '',
  fr: '',
});

<MarkdownEditor.Composed
  value={values}
  onChange={setValues}
/>

Custom Toggle

Override the default English/French toggle with custom language items.

<MarkdownEditor.Composed
  toggleItems={[
    { value: 'en', label: 'EN' },
    { value: 'fr', label: 'FR' },
    { value: 'es', label: 'ES' },
  ]}
  defaultToggleValue="en"
/>

Compound

Full compound mode with custom toolbar layout using the useMarkdownFormatting hook.

const [previewing, setPreviewing] = useState(false);
const [value, setValue] = useState('');
const ref = useRef<HTMLTextAreaElement>(null);
const format = MarkdownEditor.useMarkdownFormatting(ref, setValue);

<MarkdownEditor.Root previewing={previewing}>
  <MarkdownEditor.Toolbar>
    <MarkdownEditor.ToolbarGroup>
      <MarkdownEditor.ToolbarButton onClick={() => format('bold')}>B</MarkdownEditor.ToolbarButton>
    </MarkdownEditor.ToolbarGroup>
    <MarkdownEditor.Toggle items={MarkdownEditor.DEFAULT_TOGGLE_ITEMS} defaultValue="en" />
  </MarkdownEditor.Toolbar>
  <MarkdownEditor.Content ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
</MarkdownEditor.Root>

With FormField

Inside a FormField with label, required indicator, and hint.

Describe your business in detail.
<FormField.Root label="Description" required hint="Describe in detail.">
  <MarkdownEditor.Composed />
</FormField.Root>

With Error

Error state inside a FormField.

<FormField.Root label="Description" error="Required.">
  <MarkdownEditor.Composed hasError />
</FormField.Root>

Disabled

Disabled editor with muted toolbar and content.

<FormField.Root label="Description" disabled>
  <MarkdownEditor.Composed disabled />
</FormField.Root>

With Default Content

Editor pre-filled with per-language markdown content. Toggle between languages to see different content. Click preview to see rendered markdown.

<MarkdownEditor.Composed
  defaultValue={{
    en: '# Hello\n\nThis is **bold** text.',
    fr: '# Bonjour\n\nCeci est du texte en **gras**.',
  }}
/>

API Reference

MarkdownEditor.Root

The root container. Provides context for hasError, disabled, and previewing state.

PropTypeDefaultDescription
hasErrorbooleanfalseError state applied to the content area.
disabledbooleanfalseDisables toolbar and content.
previewingbooleanfalseWhether the editor is in preview mode.

MarkdownEditor.Toolbar

The toolbar container with role='toolbar'.

PropTypeDefaultDescription

MarkdownEditor.ToolbarButton

An individual toolbar button with hover and active states.

PropTypeDefaultDescription
activebooleanfalseWhether the button is active/pressed.
tooltipstring-Tooltip label shown on hover (xsmall size).

MarkdownEditor.ToolbarGroup

Groups toolbar buttons with consistent spacing.

PropTypeDefaultDescription

MarkdownEditor.Toggle

Built-in SwitchToggle.Group for the toolbar. Used in compound mode.

PropTypeDefaultDescription
itemsSwitchToggleGroupItem[]-Array of toggle items.
valuestring-Controlled active value.
defaultValuestring-Default active value.
onValueChange(value: string) => void-Callback on change.

MarkdownEditor.Content

The editor content area. Renders a textarea in edit mode and rendered HTML in preview mode.

PropTypeDefaultDescription
hasErrorboolean-Override error state from context.
heightstring'200px'Min-height of the editor content area.
valuestring-The markdown content string for the current language.

MarkdownEditor.Composed

Props-driven composed mode with two variants: multi-language (default, with English/French toggle) and single-language (toggleItems={false}). In multi-language mode, value/onChange use a `LocalizedValue` object (`{ en: "", fr: "" }`). In single-language mode, value/onChange use a plain string.

PropTypeDefaultDescription
toggleItemsSwitchToggleGroupItem[] | falseDEFAULT_TOGGLE_ITEMSPass false to hide the toggle (single-language mode). Pass an array to override the default English/French items. Omit for default English/French toggle.
valueLocalizedValue | string-Controlled value. Object when toggle is shown, string when toggleItems={false}.
defaultValueLocalizedValue | string-Initial uncontrolled value. Object when toggle is shown, string when toggleItems={false}.
onChange(values: LocalizedValue) => void | (markdown: string) => void-Callback on change. Receives the full values object in multi-language mode, or a plain string in single-language mode.
placeholderstring-Placeholder text for the textarea.
hasErrorbooleanfalseError state.
disabledbooleanfalseDisabled state.
toggleValuestring-Controlled active language (multi-language mode only).
defaultToggleValuestring'en'Default active language (multi-language mode only).
onToggleChange(value: string) => void-Callback when the active language changes (multi-language mode only).
containerClassNamestring-ClassName for the root container.
contentClassNamestring-ClassName for the content area.
heightstring'200px'Min-height of the editor content area.

useMarkdownFormatting

Hook that returns a function to apply markdown formatting actions to a textarea. Use with compound mode for custom toolbar buttons.

PropTypeDefaultDescription
textareaRefRefObject<HTMLTextAreaElement | null>-Ref to the textarea element.
onChange(value: string) => void-Callback when the value changes from formatting.

renderMarkdown

Converts a markdown string to HTML using marked with GFM support. Used internally for preview mode but exported for custom use.

PropTypeDefaultDescription
mdstring-The markdown string to render.

Previous
Label
Next
Radio