Candor provider
ProWraps an app in Candor's providers from one config: LocaleProvider (with the app's nouns), EntityKindsProvider, AgentRolesProvider, CardRegistryProvider and TooltipProvider, and LinkProvider for link. It always renders all of them, whatever the config holds, so a setting that arrives after the first render (a locale, a preference) doesn't remount the app; a setting left out passes through what an outer provider set, or the default. It renders nothing of its own. Charts' ChartSizeProvider is not among them; nest it inside where charts need a width before they can measure.
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 and 2.7.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
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/candor-provider
Brings in: agent-avatar, card-registry, entity-chip, link, locale, tokens, tooltip · npm: lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { CandorProvider, type CandorConfig } from "@/components/candor/candor-provider"
import { AgentBadge } from "@/components/candor/agent-avatar"
import { Composer } from "@/components/candor/composer"
import { EntityChip } from "@/components/candor/entity-chip"
import { useStrings } from "@/components/candor/locale"
import { RelativeTime } from "@/components/candor/relative-time"
/**
* One object for the whole app. It is plain data, so a server component (a root layout) can build it, or a coding
* agent can write it as JSON; every field is optional.
*/
const config: CandorConfig = {
locale: "en-GB",
timeZone: "Europe/London",
nouns: { environment: { one: "workspace", other: "workspaces" }, environments: "workspaces", account: "team", product: "Acme" },
entityKinds: { ticket: { noun: "Ticket", iconFrom: "record" } },
agentRoles: { triager: { noun: "Triager", iconFrom: "reviewer" } },
strings: { composer: { placeholder: "Ask the triager about a ticket" } },
tooltip: { delay: 300 },
}
const at = new Date(Date.now() - 12 * 60_000).toISOString()
/** `config.nouns` fills every message that names a noun, Candor's and the app's own, in the form the count picks. */
const scopeStrings = {
scope: "{Product} keeps {count} {environment} for your {account}",
}
function Scope() {
const t = useStrings("scope", scopeStrings)
return <p className="text-sm text-muted-foreground">{t("scope", { count: 3 })}</p>
}
export default function Example() {
const [value, setValue] = React.useState("")
return (
<CandorProvider config={config}>
<div className="flex max-w-lg flex-col gap-3">
<p className="flex flex-wrap items-center gap-2 text-sm">
<AgentBadge agent={{ id: "t1", name: "Intake", role: "triager" }} size="sm" /> picked up
<EntityChip entity={{ kind: "ticket", name: "T-1042", label: "Refund not received" }} onOpen={() => {}} />
<RelativeTime date={at} />
</p>
<Scope />
<Composer value={value} onValueChange={setValue} onSubmit={() => setValue("")} />
</div>
</CandorProvider>
)
}<CandorProvider>
| Prop | Type | Default | Description |
|---|---|---|---|
| config | CandorConfig | undefined | — | The app's settings as plain data; every field is optional. A server component can pass it. |
| kinds | Record<string, EntityKindInfo> | undefined | — | Entity kinds with their own icons, such as a preset (`githubKinds`). Code, not data, so pass it from a client component. Applied after `config.entityKinds`, so it wins for an id both set. |
| roles | Record<string, AgentRoleInfo> | undefined | — | Agent roles with their own glyphs. Code, not data, like `kinds`; applied after `config.agentRoles`. |
| cards | Record<string, AnyCard> | undefined | — | The app's cards by name, made with defineCard. Code, not data, like `kinds`. Left out, a card registry set above still applies. |
| link | LinkRender | undefined | — | How items draw a link to an address in the app, so the app's router follows it: `(props) => <NextLink {...props} />`. Code, not data, like `kinds`; define it once outside a component. Left out, a LinkProvider above sets it, else items draw a plain `<a>`. |
| childrenrequired | React.ReactNode | — | What goes inside. |
Types
- EntityKindConfig
- An entity kind written as data: a noun, and the built-in kind whose icon it borrows.
{ /** What one of these is called in the app: "Ticket", "Case". */ noun: string; /** * A built-in kind whose icon this kind uses: "record", "document", "file", "table", "field", "person", "link", * "code", "workflow", "report" or "dashboard". Left out, a built-in id keeps its own icon; any other kind, or a name * that isn't one of those, gets the plain box icon an unregistered kind gets. */ iconFrom?: string | undefined; }- AgentRoleConfig
- An agent role written as data: a noun, the built-in role whose glyph it borrows, and its hue.
{ /** What an agent in this role is called: "Triager". */ noun: string; /** * A built-in role whose glyph (and, unless `hue` is set, colour) this role uses: "assistant", "orchestrator", * "planner", "researcher", "analyst", "designer", "coder", "reviewer" or "tester". Left out, a built-in id keeps its * own glyph; any other role, or a name that isn't one of those, is drawn like "assistant". */ iconFrom?: string | undefined; /** A CSS colour for the role's tile, such as "var(--chart-2)". Left out, it is the `iconFrom` role's hue. */ hue?: string | undefined; }- TooltipConfig
- How tooltips open and close across the app, in TooltipProvider's (Base UI's) names.
{ /** Milliseconds from hover to open. Left out, 0: Candor's tooltips open at once. */ delay?: number | undefined; /** Milliseconds from the pointer leaving to close. Left out, Base UI's 0. */ closeDelay?: number | undefined; /** Milliseconds after one tooltip closes during which the next opens with no delay. Left out, Base UI's 400. */ timeout?: number | undefined; /** When true, the tooltip closes as the pointer leaves the trigger, even towards the tooltip. Left out, false. */ disableHoverablePopup?: boolean | undefined; }- CandorConfig
- Everything CandorProvider sets, as plain data: no functions or components, so a server component (a Next.js root layout, say) can build it and pass it down, and a coding agent can write it as JSON. Every field is optional; a field left out leaves that setting at its default, or at whatever a provider above already set.
{ /** A BCP 47 tag such as "en-GB", "fr" or "ar-EG", for dates, numbers and plural forms. Left out, the browser's own once the page has hydrated. */ locale?: string | undefined; /** Text direction. Left out, it follows `locale`: "rtl" for Arabic, Hebrew, Persian, Urdu and the like. Set `dir` on `<html>` too. */ dir?: TextDirection | undefined; /** An IANA time zone such as "Europe/Paris". With `locale`, server and browser format dates the same way. */ timeZone?: string | undefined; /** Interface strings by component name, e.g. `{ thread: { jumpToLatest: "…" } }`; any left out stay English. */ strings?: StringOverrides | undefined; /** * The app's nouns, which fill `{environment}`, `{account}`, `{product}` and the like in every message: * `{ environment: { one: "workspace", other: "workspaces" }, account: "team" }`; any left out stay Candor's, and * `environments` defaults to the `other` form of an `environment` given without it. LocaleProvider's `nouns`. */ nouns?: Partial<NounTable> | undefined; /** The app's entity kinds by id, added to the built-in ones; an id that is already a kind replaces its noun. */ entityKinds?: Record<string, EntityKindConfig> | undefined; /** The app's agent roles by id, added to the built-in ones; an id that is already a role replaces it. */ agentRoles?: Record<string, AgentRoleConfig> | undefined; /** Tooltip timing for the whole app. A setting left out keeps what a TooltipProvider above set, or Candor's default. */ tooltip?: TooltipConfig | undefined; }
Types from other items
The props above take these. Each is defined by another item; import it from the path shown.
- LinkRender from @/lib/candor/link
- Draws a link the app's router follows. It is called as a function while an item draws, so it returns an element and calls no hooks itself: `(props) => <NextLink {...props} />` for Next.js, `({ href, ...props }) => <Link to={href} {...props} />` for TanStack Router.
(props: LinkRenderProps) => React.ReactElement- TextDirection from @/components/candor/locale
"ltr" | "rtl"- StringOverrides from @/components/candor/locale
- Overrides by component name, e.g. `{ thread: { jumpToLatest: "…" }, composer: { send: "…" } }`.
Record<string, Partial<StringTable> | undefined>- NounTable from @/lib/candor/fill-strings
- The app's nouns by name; a message names one as `{environment}`, or `{Environment}` at the start of a sentence.
Record<string, Noun>- LinkRenderProps from @/lib/candor/link
- What an item hands the app's link render: the address, what the link holds, and every other attribute the item's `<a>` would have had (its classes, `data-slot`, `aria-*`, handlers and ref). Pass them all on.
Omit<React.ComponentPropsWithRef<"a">, "href" | "className" | "children"> & { /** Where the link goes: an address in the app ("/runs/42"), as the app gave it to the item. */ href: string; /** The item's classes for the link. */ className?: string | undefined; /** What the link holds: its words, an icon. */ children?: React.ReactNode | undefined; /** The item's `data-*` attributes (its `data-slot`, say), which the app's link passes on with the rest. */ [data: `data-${string}`]: string | number | boolean | undefined; }- StringTable from @/components/candor/locale
- A component's strings: its English defaults, or your overrides.
Record<string, Phrase>- Noun from @/lib/candor/fill-strings
- One of the app's nouns: a word ("workspace"), or its plural forms (`{ one: "workspace", other: "workspaces" }`), with a `gender` for messages that select on it and a `start` for the start of a sentence where upper-casing the first letter isn't how the language does it.
string | (PluralPhrase & { gender?: string | undefined; start?: string | PluralPhrase | undefined })