# System Prompt Editor

System Prompt Editor [#system-prompt-editor]

The **System Prompt Editor** is a controlled editor for a chat/completion system
prompt with quick-pick presets. Selecting a preset replaces the textarea value;
free-form edits that match no preset deselect all presets. It composes the
[Option List](/docs/input-controls/option-list) primitive for the presets and
exports `SYSTEM_PROMPT_PRESETS` and `DEFAULT_SYSTEM_PROMPT` as sensible defaults.

> **Presentational (value in, `onChange` out).** Persistence and model wiring
> stay in the consumer — the editor never touches storage. Recommended
> destination: the `systemPrompt` you pass to `useChat` / `useGenerateText`
> (a recommendation, never a requirement).

**When to use it:** any chat or generation surface that lets the user set or
switch the assistant's system prompt with a few opinionated presets.

Preview [#preview]

```tsx
'use client';

import { useState } from 'react';

import { SystemPromptEditor, DEFAULT_SYSTEM_PROMPT } from '@/components/system-prompt-editor';

/**
 * Demo for SystemPromptEditor, used by the docs live preview. Controlled — pick
 * a preset to replace the value, or type freely to deselect all presets. The
 * consumer owns persistence; here the value simply lives in local state.
 */
export default function SystemPromptEditorDemo() {
  const [prompt, setPrompt] = useState(DEFAULT_SYSTEM_PROMPT);

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <SystemPromptEditor value={prompt} onChange={setPrompt} />
      <p className="text-xs text-muted-foreground">
        {prompt.length} characters - applied on the next chat turn in a real app.
      </p>
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/input-controls/system-prompt-editor
```

Dependencies [#dependencies]

* **Data source:** fully controlled — emits `onChange(value)`; the consumer owns persistence and applies the value to a model. Recommended destination: `useChat` / `useGenerateText` `systemPrompt` (optional).
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)
* Composes `@localmode/ui/input-controls/option-list` (installed automatically as a registry dependency)

Files installed [#files-installed]

* `system-prompt-editor.tsx` — the component (exports `SYSTEM_PROMPT_PRESETS`, `DEFAULT_SYSTEM_PROMPT`)
* `option-list.tsx` — the composed preset list
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**SystemPromptEditor**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | — | **Required.** The current system prompt (controlled). |
| `onChange` | `function` | — | **Required.** Fired with the new prompt on every edit or preset selection. |
| `ariaLabel` | `string` | `"System prompt"` | Accessible name for the textarea, applied as its `aria-label` so the control has a programmatic name independent of the visible heading. |

**SystemPromptPreset**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | — | **Required.** Stable preset identifier. |
| `label` | `string` | — | **Required.** Short visible label. |
| `description` | `string` | — | **Required.** One-line description shown under the label. |
| `prompt` | `string` | — | **Required.** The full system prompt applied on selection. |

Examples [#examples]

Wire to a chat system prompt (consumer owns persistence) [#wire-to-a-chat-system-prompt-consumer-owns-persistence]

```tsx
import { useState } from 'react';
import {
  SystemPromptEditor,
  DEFAULT_SYSTEM_PROMPT,
} from '@/components/system-prompt-editor';

export function PromptPanel() {
  const [prompt, setPrompt] = useState(
    () => localStorage.getItem('systemPrompt') ?? DEFAULT_SYSTEM_PROMPT,
  );
  return (
    <SystemPromptEditor
      value={prompt}
      onChange={(v) => {
        setPrompt(v);
        localStorage.setItem('systemPrompt', v); // persistence is yours
      }}
    />
  );
}
```

Extend the presets [#extend-the-presets]

`SYSTEM_PROMPT_PRESETS` is exported — spread it and add your own before rendering,
or edit the copied file directly.

Accessible name [#accessible-name]

The textarea carries an `aria-label` (default `"System prompt"`) so it has a
programmatic name independent of the visible heading. Override `ariaLabel` if
your surrounding copy names the field differently.

```tsx
<SystemPromptEditor value={prompt} onChange={setPrompt} ariaLabel="Assistant instructions" />
```

Customization [#customization]

The editor is purely presentational: value in, `onChange` out. Because you own
the copied file, add or remove presets, change the textarea rows, or attach your
own persistence and `useChat` wiring — the primitive stays free of orchestration
state.