/**
* UI primitives for `cursor/canvas`. Styling follows the Cursor dark theme; no extra packages required.
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
/**
* Shallow-merge style objects with `override` taking precedence.
*
* Use for small tweaks on built-in components (e.g. extra padding or width).
* **Do not** use this to build elaborate custom chrome — prefer the built-in
* components and flat solid token colors. No gradients, no box-shadows.
*
* @example
* ```tsx
* // Good — minor override on a built-in component
* …
*
* // Bad — hand-rolled decorative styling
*
…
* ```
*/
export declare function mergeStyle(base: CSSProperties, override?: CSSProperties): CSSProperties;
export type StackProps = {
children?: ReactNode;
gap?: number;
style?: CSSProperties;
};
/**
* Vertical flex column. Use as the top-level page wrapper or to stack cards/sections.
*
* @example
* ```tsx
*
*
Dashboard
* …
* …
*
* ```
*/
export declare function Stack({ children, gap, style }: StackProps): JSX.Element;
export type RowProps = {
children?: ReactNode;
gap?: number;
align?: "start" | "center" | "end" | "stretch";
justify?: "start" | "center" | "end" | "space-between";
wrap?: boolean;
style?: CSSProperties;
};
/**
* Horizontal flex row. Use for inline groups of buttons, badges, or metadata.
*
* @example
* ```tsx
*
*
*
*
* ```
*/
export declare function Row({ children, gap, align, justify, wrap, style }: RowProps): JSX.Element;
/**
* CSS Grid with tokenized gap. Prefer this over `Row` + `wrap` when you need a
* fixed number of equal-width columns: wrapped flex items can land on their
* own row and grow to full width (`flex-grow`), which is often surprising for
* boards and dashboards.
*/
export type GridProps = {
children?: ReactNode;
/**
* Equal columns: pass a number (uses `repeat(n, minmax(0, 1fr))`), or a CSS
* `grid-template-columns` string (e.g. `"1fr 2fr"` or `"minmax(0, 200px) 1fr"`).
*/
columns: number | string;
gap?: number;
align?: "start" | "center" | "end" | "stretch";
style?: CSSProperties;
};
export declare function Grid({ children, columns, gap, align, style }: GridProps): JSX.Element;
export type DividerProps = {
style?: CSSProperties;
};
/**
* Horizontal line for visually separating sections. Uses `stroke.tertiary`
* to match the Card/Table hairline weight.
*
* @example
* ```tsx
*
* Section one
*
* Section two
*
* ```
*/
export declare function Divider({ style }: DividerProps): JSX.Element;
/**
* Flex spacer that pushes siblings apart. Place inside a `Row` to push
* trailing content to the right edge.
*
* @example
* ```tsx
*
* Title
*
*
*
* ```
*/
export declare function Spacer(): JSX.Element;
/** Horizontal alignment for a table column. */
export type TableColumnAlign = "left" | "center" | "right";
/** Semantic tone for a table row — renders a translucent tinted background. */
export type TableRowTone = "success" | "danger" | "warning" | "info" | "neutral";
export type TableProps = {
/** Column titles, left to right. Column count is fixed by this array. */
headers: ReactNode[];
/**
* Body rows. Each row is an array of cells in the same order as `headers`.
* Shorter rows are padded with empty cells; extra cells are ignored.
*/
rows: ReactNode[][];
/** Optional alignment per column index (headers/rows). Defaults to left. */
columnAlign?: Array;
/**
* Optional semantic tone per row index. Applies a translucent tinted
* background — use for status highlighting (e.g. failing services, warnings).
* Sparse: `undefined` entries are uncolored.
*/
rowTone?: Array;
/** When true (default), bordered rounded shell with horizontal scroll if needed. */
framed?: boolean;
/** Alternate subtle fill on even rows for easier scanning in large tables. */
striped?: boolean;
/** Stick the header row when the framed container scrolls vertically. */
stickyHeader?: boolean;
style?: CSSProperties;
/** Shown in a single spanning cell when `rows` is empty. */
emptyMessage?: ReactNode;
};
/**
* Data table with column headers and rows. Framed by default with its own
* bordered container — **do not wrap in a Card** unless the card itself is
* a named entity that happens to contain a table. Render directly under a
* heading in the normal case.
*
* @example
* ```tsx
* // Good — table directly under a heading
*
*
* ```
*/
export declare function Table({ headers, rows, columnAlign, rowTone, framed, striped, stickyHeader, style, emptyMessage }: TableProps): JSX.Element;
export type TextWeight = "normal" | "medium" | "semibold" | "bold";
export type TextProps = {
children?: ReactNode;
tone?: "primary" | "secondary" | "tertiary" | "quaternary";
size?: "body" | "small";
/**
* Element tag to render. Defaults to `"p"` for top-level body copy and
* automatically switches to `"span"` when nested inside another typography
* container so inline emphasis stays valid HTML.
*/
as?: "p" | "span";
/** Font weight. Default is `"normal"` (400). Use `"semibold"` or `"bold"` for emphasis. */
weight?: TextWeight;
/** Render as italic. */
italic?: boolean;
/**
* Truncate overflowing text with an ellipsis on a single line.
* - `true` / `"end"` — ellipsis at the end (default truncation).
* - `"start"` — ellipsis at the start. Useful for file paths where the
* filename matters more than the directory prefix.
*
* Requires the parent to have a bounded width (flex child with
* `minWidth: 0`, fixed width, etc.) — otherwise the text just expands
* and never overflows.
*/
truncate?: boolean | "start" | "end";
style?: CSSProperties;
};
/**
* Body text with tone, size, weight, and italic variants.
*
* Top-level `Text` renders a `
`. Nested `Text` automatically renders a
* `` so inline emphasis like `Use this`
* does not emit invalid nested paragraphs. Use `as` to override when needed.
*
* Compose with `` for inline code and `` for hyperlinks inside
* the text flow.
*
* @example
* ```tsx
* Primary body text.
* Important note.
* Supplementary remark.
* Run npm install to get started.
* See the docs for details.
* ```
*/
export declare function Text({ children, tone, size, as, weight, italic, truncate, style }: TextProps): JSX.Element;
export type H1Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Page-level heading. Use once at the top of a canvas.
* **Do not** place inside `CardHeader` — card headers use their own label.
*
* @example
* ```tsx
*
*
Performance Report
* …
*
* ```
*/
export declare function H1({ children, style }: H1Props): JSX.Element;
export type H2Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Section heading. Use between groups of cards or sections.
* **Do not** place inside `CardHeader` — card headers use their own label.
*
* @example
* ```tsx
*
*
* All requests require a bearer token.
*
* ```
*/
export declare function H3({ children, style }: H3Props): JSX.Element;
export type CodeProps = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Inline `` span for identifiers, file names, or short snippets.
* Uses `0.92em` so it scales with surrounding text (headings, body, etc.).
*
* Prefer writing backtick markdown inside `Text` — e.g. `` Run `npm install` `` —
* which is automatically parsed. Use `` only when you need an explicit element.
*
* @example
* ```tsx
* Run npm install to get started.
* ```
*/
export declare function Code({ children, style }: CodeProps): JSX.Element;
export type LinkProps = {
children?: ReactNode;
href: string;
style?: CSSProperties;
};
/**
* Inline link that opens in the user's default browser.
*
* Prefer writing markdown links inside `Text` — e.g. `See the [docs](url)` —
* which are automatically parsed. Use `` when you need an explicit anchor
* outside of a text flow or when composing with other elements.
*
* @example
* ```tsx
* View documentation
* ```
*/
export declare function Link({ children, href, style }: LinkProps): JSX.Element;
export type CardSize = "base" | "lg";
export type CardVariant = "default" | "borderless";
/**
* Inline chevron SVG used by disclosure-style controls (collapsible cards,
* expandable list items, etc.). Shared by `Card` and `todo-list.tsx` so
* every disclosure in the canvas SDK uses the same glyph.
*/
export declare function CanvasChevron({ expanded }: {
expanded: boolean;
}): JSX.Element;
export type CardProps = {
children?: ReactNode;
/** Default: bordered surface with radius; `borderless` removes both. */
variant?: CardVariant;
/** `lg` uses a taller header and roomier title padding (matches packages/ui). */
size?: CardSize;
/**
* When true, the header uses `position: sticky` so it stays visible while
* the card body scrolls. Requires the card (or a parent) to have a
* constrained height and `overflow: auto` — the canvas host controls this,
* so sticky behavior depends on the host viewport.
*/
stickyHeader?: boolean;
/**
* Make the card collapsible. The header becomes a clickable toggle with
* a leading chevron; `CardBody` renders nothing while the card is closed.
*/
collapsible?: boolean;
/** Initial open state in uncontrolled mode. Ignored when `open` is set. */
defaultOpen?: boolean;
/** Controlled open state. Pair with `onOpenChange`. */
open?: boolean;
/** Fires on every toggle with the next open state. */
onOpenChange?: (open: boolean) => void;
style?: CSSProperties;
};
/**
* Bordered surface for a **labeled, self-contained unit** — a file, a service,
* a config block, or a table with a title. Compose with `CardHeader` + `CardBody`.
*
* **When to use Card:**
* - Displaying a named entity (file path, service name, resource).
* - Wrapping a `
` or `` that needs a title.
* - A distinct, bounded section the user might scan by header label.
*
* **When NOT to use Card:**
* - General text sections — use `
` + `` instead. Not every section
* needs a border.
* - Page-level layout — use `` with headings. A canvas should not be a
* wall of stacked cards.
* - Nesting — do not put cards inside cards. Use `` within a card body.
*
* Pass **plain text** as `CardHeader` children — the header provides its own
* 12px font. Do **not** put `
` or `
` inside a card header.
*
* Set `collapsible` to make the header a toggle that shows/hides `CardBody`.
*
* @example
* ```tsx
* // Card wraps a titled diff
*
* }>
* src/utils.ts
*
*
*
*
*
*
* // Collapsible card
*
* deploy-service.ts
* Service handles rolling deployments across regions.
*
*
* // Bad — card wrapping plain text that should just be a heading
* // Use
Overview
… instead.
* ```
*/
export declare function Card({ children, variant, size, stickyHeader, collapsible, defaultOpen, open: openProp, onOpenChange, style }: CardProps): JSX.Element;
export type CardHeaderProps = {
/** Plain text title. Do **not** pass headings, buttons, pills, or layout rows. */
children?: ReactNode;
/** Small trailing content aligned to the right edge — a status label, a
* single pill, or a short metadata string. Keep it compact. */
trailing?: ReactNode;
style?: CSSProperties;
};
/**
* 28px header row (32px at `size="lg"`). A compact label for the card.
*
* **`children`** — plain text only. This is a 12px label, not a toolbar.
* Do **not** pass `