Skip to content
Tier

Code editor

Pro

A small editor for code or a query a person writes or corrects: line numbers, the same token colours as CodeListing and Payload drawn under the text, Tab and Shift+Tab to indent, and a validation message from your app tied to its line and read out with the field (aria-describedby). The text is a plain textarea underneath, so typing, undo, selection and screen readers behave as they do in any field. It reads left to right whatever the page's direction unless dir says otherwise, and its colours come from the theme's tokens.

It isn't a full code editor: no autocomplete, linting, folding, search or multiple cursors, and its colours come from a light tokenizer rather than a parser. For those, embed Monaco or CodeMirror themed from the same tokens (the "Code editor" pattern, /patterns/code-editor-embedding). Nothing in the text is run or rendered as markup; what the person types reaches your app as a string, and your server decides whether to run it.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0 and 2.8.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/code-editor

Brings in: bidi, code-listing, locale, tokens, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { CodeEditor, type CodeEditorError } from "@/components/candor/code-editor"
import { PermissionPrompt, type PermissionRequest } from "@/components/candor/permission-prompt"

const start = `{
  "table": "orders",
  "where": { "status": "settled" },
  "limit": 50
}`

/** Your app's validation: here, JSON.parse, with the line worked out from the position it reports. */
function check(text: string): CodeEditorError | undefined {
  try {
    const v = JSON.parse(text) as unknown
    return v && typeof v === "object" && !Array.isArray(v) ? undefined : { message: "That isn’t a JSON object, so it can’t be used." }
  } catch (e) {
    const at = /position (\d+)/.exec(String(e))
    const line = at ? text.slice(0, Number(at[1])).split("\n").length : undefined
    return { message: "That isn’t valid JSON.", line }
  }
}

const request: PermissionRequest = {
  id: "p1",
  tool: "Bash",
  title: "The agent wants to run npm test -- invoices",
  reason: "No rule allows Bash commands yet.",
  input: { command: "npm test -- invoices", timeout: 120000 },
}

export default function Example() {
  const [value, setValue] = React.useState(start)
  const [said, setSaid] = React.useState<string>()
  return (
    <div className="flex max-w-lg flex-col gap-6">
      <div className="flex flex-col gap-2">
        <CodeEditor highlight label="Query, as JSON" language="json" value={value} onValueChange={setValue} error={check(value)} minLines={6} />
        <p className="text-xs text-muted-foreground">Delete a comma to see the message tied to its line.</p>
      </div>
      {/* PermissionPrompt (free) edits the input in a plain field; pass CodeEditor and it edits in this one. */}
      <PermissionPrompt
        request={request}
        editable
        jsonEditor={CodeEditor}
        onAllow={(a) => setSaid(`Allowed${a.updatedInput ? " as edited" : ""}.`)}
        onDeny={() => setSaid("Denied.")}
      />
      <p role="status" className="text-sm text-muted-foreground">{said}</p>
    </div>
  )
}

<CodeEditor>

PropTypeDefaultDescription
valuerequiredstring—The text, as the person has it.
onValueChangerequired(value: string) => void—Called with the new text on each edit.
languagerequiredstring—The language, for its colours: "json", "sql", "yaml", "ts"; "text" for none. Never run.
labelrequiredstring—The field's name, shown above it.
labelHiddenboolean | undefinedfalseKeeps the label for screen readers only, for a frame that already names the field.
errorCodeEditorError | undefined—What's wrong, from your app's validation, and its line. The field is marked invalid and the message is read with it.
wrapboolean | undefinedfalseWrap long lines instead of scrolling sideways, for prose such as instructions. Defaults to false: code keeps its lines.
dir"ltr" | "rtl" | "auto" | undefined"ltr"The text's direction. Code reads left to right in every language, so it defaults to "ltr"; "auto" follows the text, for prose.
indentstring | undefined" "What Tab inserts and Shift+Tab removes. Defaults to two spaces.
minLinesnumber | undefined4The fewest lines tall it is. Defaults to 4.
maxLinesnumber | undefined20The most lines tall it grows before it scrolls. Defaults to 20.
maxLengthnumber | undefined—Most characters allowed.
placeholderstring | undefined—Shown while the field is empty.
footerReact.ReactNode | undefined—Inside the frame, under the text: a toolbar or a count.
highlightboolean | undefined—Colour the code by its syntax. Left out, it follows the nearest `data-syntax` attribute (off by default).
refReact.Ref<HTMLTextAreaElement> | undefined—The textarea, for placing the caret or inserting text from outside.
classNamestring | undefined—Extra classes for the outer element.

Functions

indentEdit(value: string, start: number, end: number, indent: string, outdent: boolean)

The edit that Tab (or Shift+Tab, with outdent) makes: the range it replaces, what goes there, and the selection after. With nothing selected Tab types one indent at the caret; with lines selected, or for Shift+Tab, each line in the selection moves by one indent.

Strings

The fixed words, in English by default. Change any of them under codeEditor in <LocaleProvider strings={{ … }}>; the table is exported as codeEditorStrings.

KeyEnglish
hintTab indents and Shift+Tab outdents. To move on with the keyboard, press Esc, then Tab (or Shift+Tab to go back).
lineErrorLine {line}: {message}

Types

CodeEditorError
What's wrong with the text, in the app's words, and the line it's on when the app knows.
{ /** The app's sentence: "That isn't a JSON object, so it can't be used." Shown as text. */ message: string; /** The line it's on, counting from 1. Left out, the message is about the whole text. */ line?: number | undefined; }