@cubster/editor
v0.2.0
Published
A keyboard-driven, markdown-aware text editor component for React, built for Cubster
Maintainers
Readme
@cubster/editor
A keyboard-driven, markdown-aware text editor component for React.
Install
npm install @cubster/editorUsage
import { useState } from 'react';
import { Editor } from '@cubster/editor';
function App() {
const [text, setText] = useState('');
return (
<Editor
value={text}
onChange={setText}
placeholder="Start writing..."
className="w-full h-64 p-4 bg-gray-900 text-gray-100 font-mono text-sm resize-none focus:outline-none"
/>
);
}Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | string | required | Current text value |
| onChange | (value: string) => void | required | Called when text changes |
| tabSize | number | 2 | Spaces per indent level |
| placeholder | string | 'Start writing...' | Placeholder text |
| className | string | '' | CSS class names |
| wrapperClassName | string | '' | CSS class name(s) applied to the wrapper <div> |
| onContextChange | (label: string \| null) => void | — | Called whenever the markdown context at the cursor changes (e.g. 'Heading 1', 'Blockquote', or null). Fires once on mount and is deduped — it isn't called again while the label stays the same. |
All standard <textarea> HTML attributes are also accepted.
Keyboard Shortcuts
The editor wraps its <textarea> in a <div data-cubster-editor>. Formatting shortcuts introduced in v0.2.0 are keyed off the physical key (e.code), not the character it produces — Option/Shift+digit combos yield symbols and punctuation on Mac keyboards (via e.key), so binding on e.code keeps them working regardless of layout.
| Shortcut | Action |
|----------|--------|
| Cmd/Ctrl + B | Toggle bold (**text**) |
| Cmd/Ctrl + I | Toggle italic (_text_) |
| Cmd/Ctrl + Shift + X | Toggle strikethrough (~~text~~) |
| Cmd/Ctrl + Alt + E | Toggle inline code (`text`) |
| Cmd/Ctrl + Alt + Shift + E | Wrap selection in a fenced code block |
| Cmd/Ctrl + Alt + 1–6 | Toggle heading level 1–6 |
| Cmd/Ctrl + Alt + 0 | Clear heading/blockquote/list formatting back to a plain paragraph |
| Cmd/Ctrl + Alt + Q | Toggle blockquote (> text) |
| Cmd/Ctrl + Shift + 7 | Toggle an ordered (numbered) list |
| Cmd/Ctrl + Shift + 8 | Toggle an unordered (bulleted) list |
| Cmd/Ctrl + Shift + 9 | Toggle a task list (- [ ] text) |
| Cmd/Ctrl + K (no selection) | Insert []() with cursor inside [] |
| Cmd/Ctrl + K (with selection) | Wrap selection as [text]() with cursor inside () |
| Tab | Indent |
| Shift + Tab | Unindent |
| Enter on list/task-list item | Continue list |
| Enter on empty list/task-list item | End list |
| Cmd/Ctrl + Z | Undo (native) |
| Cmd/Ctrl + Shift + Z | Redo (native) |
Heading/blockquote/list toggles share one rule: they turn OFF only if every non-blank selected line already has the exact marker, otherwise they turn ON and replace any conflicting block marker (headings, blockquotes, and lists are mutually exclusive at the start of a line). Ordered lists renumber from 1. when turned on; task lists always insert an unchecked - [ ] (upgrading a plain - bullet in place, never marking it checked). Blank lines within a selection are left untouched.
The fenced-code-block shortcut escalates the fence length so a selection containing its own backtick runs (e.g. a ``` line) is wrapped safely — the fence is always longer than the longest backtick run in the content, so the inner run can't close the block early. Info-string and indented-fence edge cases are not specially handled.
Shortcuts data
The complete shortcut set (including the legacy bold/italic/link/undo/redo/indent bindings above) is available as data, for building your own help menu or command palette:
import { shortcuts } from '@cubster/editor';
shortcuts.forEach((s) => {
console.log(s.id, s.label, s.keys.mac, s.keys.other);
});Paste URL to Link
When text is selected and you paste a valid URL (e.g. https://, ftp://, mailto:), the editor automatically formats it as a Markdown link: [selected text](url). If the pasted content is not a URL, normal paste behavior applies.
Styling
The editor is headless by default. An optional stylesheet gives the wrapped <textarea> a reasonable, brand-neutral default appearance:
import '@cubster/editor/styles.css';It defines --cub-editor-* custom properties scoped under [data-cubster-editor] — override any of them on your own selector to re-theme without touching the stylesheet:
| Variable | Controls |
|----------|----------|
| --cub-editor-font-family | Font stack |
| --cub-editor-font-size | Font size |
| --cub-editor-line-height | Line height |
| --cub-editor-color | Text color |
| --cub-editor-background | Background color |
| --cub-editor-caret-color | Caret color |
| --cub-editor-placeholder-color | Placeholder text color |
| --cub-editor-selection-background | Selected-text background |
| --cub-editor-selection-color | Selected-text color |
| --cub-editor-border-color | Border color |
| --cub-editor-border-width | Border width |
| --cub-editor-border-radius | Border radius |
| --cub-editor-padding | Textarea padding |
| --cub-editor-focus-ring-color | Focus outline color |
License
MIT
