tsquare 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Vogeleer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The Inter font (the @fontsource/inter dependency) is licensed separately under
24
+ the SIL Open Font License 1.1.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ <p align="center"><img src="https://raw.githubusercontent.com/tsquare-js/tsquare/main/assets/logo.png" alt="tsquare" width="160"></p>
2
+
3
+ # tsquare
4
+
5
+ Wireframes in plain text: easy for LLMs to write, fast to render as SVG or PNG. A small text language for low-fidelity screens, with several devices side by side and notes beside them. Built for LLMs to write: the prompt comes from the component catalog, and errors come back with line numbers so a model can fix its own output.
6
+
7
+ ```tsquare
8
+ board "Login"
9
+ screen phone "Sign in"
10
+ heading "Welcome back"
11
+ input "Email" placeholder="you@example.com"
12
+ input password "Password"
13
+ checkbox "Remember me" checked
14
+ button primary "Sign in" fullWidth
15
+ ```
16
+
17
+ Save that as `login.tsq`, then:
18
+
19
+ ```bash
20
+ npx tsquare render login.tsq -o login.png --scale 2
21
+ ```
22
+
23
+ Requires Node 20 or newer. To work on tsquare itself, clone the repo, run `npm install`, and use `npm run render -- examples/notes-mobile.tsq -o notes.png --scale 2`.
24
+
25
+ ## Docs
26
+
27
+ [Getting started](docs/getting-started.md) · [The language](docs/language.md) · [Components](docs/components/README.md) · [Icons](docs/icons.md) · [Colors](docs/colors.md) · [Using it with AI](docs/ai.md)
28
+
29
+ ## Playground
30
+
31
+ ```bash
32
+ npm run playground # http://localhost:4321
33
+ ```
34
+
35
+ - **Editor:** live render as you type, problems listed by line (click one to jump there), and SVG/PNG export. Paste a whole model reply and it keeps just the code block.
36
+ - **Components:** every component with its props and a rendered example you can open in the editor. It's generated from the catalog, so it can't drift from what the renderer accepts.
37
+ - **Prompt:** the system prompt for models, with a copy button.
38
+
39
+ It renders on a local Node server using the same code as the CLI.
40
+
41
+ ## The language
42
+
43
+ One element per line; children are indented two spaces under their parent. The first line is the board.
44
+
45
+ | You write | It means |
46
+ |---|---|
47
+ | `button "Sign in"` | a quoted string sets the main text (label, title, text…) |
48
+ | `button primary lg` | bare words set options: `phone`, `desktop`, `primary`, `ghost`, `row`, `sm`, `left`, `bottom`, `password`… |
49
+ | `checkbox checked`, `toggle off` | a prop name turns a boolean on; `off` / `unchecked` / `no-<prop>` turn it off |
50
+ | `stack width=240 gap=8` | `key=value` sets any prop |
51
+ | `tabs items=[All notes, Pinned]` | lists in `[ ]`, items separated by commas; an item can contain spaces |
52
+ | `data=[["$1,200", "Smith, J"]]` | quote an item that contains a comma (`\"` for a quote inside quotes) |
53
+ | `tabbar items=[{label=Home icon=home}]` | objects in `{ }` |
54
+ | `# note to self` | comment |
55
+
56
+ If a bare word could mean two props (for example `start`, which is both an `align` and a `justify` value), write it as `key=value`. Code fences (```` ``` ````) are ignored, so a model's reply can be rendered as-is.
57
+
58
+ **Structure**
59
+
60
+ - The top element is a **board**. Its children are **screens**, plus optional **notes** beside them.
61
+ - A screen stacks its children vertically. A **navbar** is pinned to the top and a **tabbar** to the bottom.
62
+ - **modal** and **drawer** are overlays and must be direct children of a screen.
63
+
64
+ ## Colors
65
+
66
+ Wireframes are grayscale. There are three deliberate exceptions in the UI itself:
67
+
68
+ | You write | It colors |
69
+ |---|---|
70
+ | `board "App" accent=blue` | primary buttons, solid badges, checked checkboxes and radios, toggles that are on, the active tab and tab-bar item, ghost button text |
71
+ | `badge "Failed" tone=danger` | one badge: `success`, `warning` or `danger` |
72
+ | `input "Email" error helper="Required"` | the field's border and helper text, in red |
73
+
74
+ `accent` takes `blue`, `indigo`, `violet`, `pink`, `red`, `orange`, `green` or `teal`, or a hex color like `#1a73e8`. With a light hex color, text on it switches to dark and accent-colored text is darkened so it stays readable. There are no other UI color props, on purpose.
75
+
76
+ Sticky notes beside the wireframe have their own colors, since they're annotations rather than part of the UI: `note "TBD" color=pink` (`yellow` is the default, plus `blue`, `pink` and `green`). See [Colors](docs/colors.md) for more.
77
+
78
+ ## Components
79
+
80
+ | Group | Components |
81
+ |---|---|
82
+ | Canvas | board, screen (`phone` 390×844, `tablet` 820×1180, `desktop` 1280×800, `custom`), note |
83
+ | Layout | stack, grid, card, divider, spacer |
84
+ | Content | heading, text (`lines=3` draws placeholder lines), image (X-box placeholder), icon (any [Lucide](https://lucide.dev/icons) name, see [Icons](docs/icons.md)), avatar, badge |
85
+ | Controls | button, input (`multiline=4` for a textarea), checkbox, radio, toggle, select |
86
+ | Navigation & data | navbar, tabbar, tabs, list, listitem, table |
87
+ | Overlays | modal, drawer (`left`, `right`, `bottom` sheet) |
88
+
89
+ Each component has its own page with a rendered example and its props: see [Components](docs/components/README.md).
90
+
91
+ ## CLI
92
+
93
+ ```bash
94
+ tsquare render <file.tsq|-> [-o out.svg|out.png] [--scale 2]
95
+ tsquare check <file.tsq|-> # problems by line number
96
+ tsquare fmt <file.tsq|-> [-w] # canonical formatting (drops comments)
97
+ tsquare prompt # system prompt for models
98
+ ```
99
+
100
+ Use `-` to read from stdin. In this repo, run them through npm: `npm run check -- file.tsq`. Files use the `.tsq` extension; the CLI reads any text file.
101
+
102
+ ## Using it with Claude (skill)
103
+
104
+ `skill/tsquare/` is a Claude skill: the syntax, the component reference, and a workflow of write → `tsquare check` → `tsquare render` → look at the PNG and fix. Copy it to `~/.claude/skills/tsquare` (all projects) or `<project>/.claude/skills/tsquare`, and make the `tsquare` command available (`npm link` in this repo). Rebuild it after catalog changes with `npm run skill`.
105
+
106
+ ## How it works
107
+
108
+ Text is parsed into a [json-render](https://github.com/vercel-labs/json-render) spec and validated against Zod schemas. It's then laid out and drawn by [Satori](https://github.com/vercel/satori) into SVG; PNG output goes through resvg.
109
+
110
+ | File | Contents |
111
+ |---|---|
112
+ | `src/text.ts` | parser |
113
+ | `src/print.ts` | formatter (spec → text) |
114
+ | `src/compile.ts` | parse + validate with line numbers, `renderWireframe` |
115
+ | `src/prompt.ts` | model prompt, generated from the catalog |
116
+ | `src/catalog.ts` | components and their props |
117
+ | `src/components.tsx` | Satori renderers |
118
+ | `src/render.ts` | validation, board sizing, SVG/PNG |
119
+ | `src/layout.ts` | colors, device sizes, spacing |
120
+
121
+ ## Measured
122
+
123
+ How often models write valid tsquare on the first try, with no repair round:
124
+
125
+ | | Sonnet | Haiku |
126
+ |---|---|---|
127
+ | Valid on the first try (20 requests) | 100% | 85% |
128
+ | Median tokens per wireframe (JSON took about 4× as many) | 184 | 210 |
129
+ | Color checks passed (7 requests) | 11/11 | 11/11 |
130
+
131
+ How it was measured, the tasks, every model output, and the scripts: [`eval/`](eval/).
132
+
133
+ ## License
134
+
135
+ MIT. The Inter font comes from the `@fontsource/inter` dependency, under the SIL Open Font License 1.1. Icons are from [Lucide](https://lucide.dev), under the ISC license.
package/bin/tsquare.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // The CLI is TypeScript (with JSX components); tsx loads it at runtime so
3
+ // `npm link` and `npx tsquare` work without a build step.
4
+ import { register } from "tsx/esm/api";
5
+
6
+ register();
7
+ await import("../src/cli.ts");
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "tsquare",
3
+ "version": "0.1.0",
4
+ "description": "Wireframes in plain text: easy for LLMs to write, fast to render as SVG or PNG.",
5
+ "keywords": [
6
+ "wireframe",
7
+ "mockup",
8
+ "ui",
9
+ "diagram",
10
+ "llm",
11
+ "ai",
12
+ "svg",
13
+ "text-to-diagram",
14
+ "claude"
15
+ ],
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/tsquare-js/tsquare.git"
20
+ },
21
+ "homepage": "https://github.com/tsquare-js/tsquare#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/tsquare-js/tsquare/issues"
24
+ },
25
+ "type": "module",
26
+ "bin": {
27
+ "tsquare": "bin/tsquare.js"
28
+ },
29
+ "files": [
30
+ "bin/",
31
+ "src/",
32
+ "skill/tsquare/",
33
+ "LICENSE",
34
+ "README.md"
35
+ ],
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "scripts": {
40
+ "render": "tsx src/cli.ts render",
41
+ "check": "tsx src/cli.ts check",
42
+ "fmt": "tsx src/cli.ts fmt",
43
+ "prompt": "tsx src/cli.ts prompt",
44
+ "playground": "tsx playground/server.ts",
45
+ "skill": "tsx skill/build.ts",
46
+ "docs": "tsx docs/build.ts",
47
+ "typecheck": "tsc",
48
+ "examples": "for f in examples/*.tsq; do tsx src/cli.ts render $f -o ${f%.tsq}.png --scale 2; done",
49
+ "eval:prompts": "tsx eval/prompts.ts",
50
+ "eval:score": "tsx eval/score.ts"
51
+ },
52
+ "dependencies": {
53
+ "@fontsource/inter": "5.3.0",
54
+ "@json-render/core": "^0.21.0",
55
+ "@json-render/image": "^0.21.0",
56
+ "@resvg/resvg-js": "^2.6.2",
57
+ "lucide": "1.48.0",
58
+ "react": "^19.3.0",
59
+ "tsx": "^4.23.15",
60
+ "zod": "^4.6.5"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^26.6.3",
64
+ "@types/react": "^19.3.0",
65
+ "gpt-tokenizer": "^4.0.0",
66
+ "typescript": "^7.0.2"
67
+ }
68
+ }
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: tsquare
3
+ description: Create low-fidelity UI wireframes and mockups (app screens, several devices side by side, boards for specs and docs) as tsquare text, then render them to PNG or SVG with the tsquare CLI. Use when asked to wireframe, mock up or sketch a UI screen, page or flow, or to add a UI mockup to a document.
4
+ ---
5
+
6
+ # tsquare wireframes
7
+
8
+ tsquare is a small text language for UI wireframe boards. You write a `.tsq` file; the `tsquare` CLI checks it and renders it to a clean, grayscale PNG or SVG.
9
+
10
+ ## Workflow
11
+
12
+ 1. **Write** the wireframe to `<name>.tsq`, following the syntax below. Every component and prop is listed in [reference.md](reference.md); read it before using a component you haven't used yet.
13
+ 2. **Check:** `tsquare check <name>.tsq`. It lists problems by line, with the valid options or a "did you mean". Fix every one and check again until it says the file is valid.
14
+ 3. **Render:** `tsquare render <name>.tsq -o <name>.png --scale 2` (use `-o <name>.svg` for docs that take SVG).
15
+ 4. **Look at the PNG** before showing it. The checker can't see layout, so check for:
16
+ - content cut off at the bottom of a screen (screens have a fixed height; nothing warns about clipping). Remove content, split it into another screen, or use `device=custom` with a larger `height`.
17
+ - cramped rows, or text squeezed into narrow columns.
18
+ - anything the request asked for that's missing.
19
+ Fix the `.tsq` and render again.
20
+ 5. **Deliver** the image path and the `.tsq` source, so the user can edit it later.
21
+
22
+ If `tsquare` isn't found, stop and ask the user how it's installed. From a checkout of the repo, `npm link` makes the command available.
23
+
24
+ ## Keep in mind
25
+
26
+ - One wireframe is one **board**: several **screens** side by side (phone, tablet, desktop or custom sizes), plus **notes** beside them. Show states or steps of a flow as separate screens.
27
+ - Wireframes are low fidelity: placeholders (`image`, `text lines=3`) beat invented copy, unless the copy matters.
28
+ - Stay grayscale unless the user asks for color or a brand. Then set `accent` on the board; don't look for other color props, there aren't any.
29
+
30
+ ## Output format: wireframe text
31
+ One element per line. Indent children two spaces under their parent. The first line is the board.
32
+
33
+ A line is the component name in lowercase, followed by arguments separated by spaces:
34
+ - "a quoted string" sets the component's main text prop (marked "main text" in reference.md)
35
+ - a bare word that is one of the component's option values sets that option: phone, desktop, primary, ghost, row, sm, left, bottom, password, …
36
+ - a bare prop name sets a boolean prop to true: checked, fullWidth, grow, muted. `off` and `unchecked` set on/checked to false
37
+ - key=value sets any prop. Values: "string", number, true/false, bare word, [list, of, values], {key=value key=value}
38
+ - # starts a comment
39
+
40
+ Lists: items are separated by commas, and an item can contain spaces without quotes: [All notes, Pinned, Shared]. To put a comma inside an item, quote the item: ["$1,200", "Smith, J"]. Inside quotes, write \" for a quote character. Numbers in a list of text are fine: [2023, 2024].
41
+
42
+ If a bare word could mean more than one prop, write it as key=value.
43
+
44
+ ## Rules
45
+ 1. The top element is a Board. The Board's children are Screens, plus optional Notes beside them.
46
+ 2. Each Screen is one view of the product. Use several Screens to show several views or states.
47
+ 3. A Screen lays out its children top to bottom. Use Stack (direction row or column) and Grid to arrange content.
48
+ 4. NavBar is pinned to the top of its Screen and TabBar to the bottom.
49
+ 5. Modal and Drawer are overlays. They must be direct children of a Screen.
50
+ 6. This is a low-fidelity wireframe. Prefer placeholders (Image boxes, Text with lines) over invented copy unless the copy matters.
51
+ 7. Only use the components and props listed below. All props are optional unless marked required.
52
+ 8. Wireframes are grayscale. The only UI colors: Board accent (one color for primary buttons, checked controls, toggles, active tabs and ghost buttons), Badge tone (success, warning, danger) and Input error. Only add an accent if the request asks for color or a brand.
53
+
54
+ ## Example
55
+
56
+ Request: Two phone screens for a recipe app: a browse screen with search, category tabs, a grid of recipe cards and a tab bar; and a recipe detail screen with its options sheet open.
57
+
58
+ ```tsquare
59
+ board "Recipe app"
60
+ screen phone "Browse"
61
+ navbar "Recipes" leading=menu actions=[bell]
62
+ input search placeholder="Search recipes"
63
+ tabs items=[All, Quick, Vegetarian] active=0
64
+ grid columns=2 gap=12
65
+ card padding=10
66
+ image height=110
67
+ text "Tomato soup" bold
68
+ text sm "25 min" muted
69
+ card padding=10
70
+ image height=110
71
+ text lines=2
72
+ tabbar items=[{label=Browse icon=book-open}, {label=Saved icon=heart}, {label=Profile icon=user}] active=0
73
+ screen phone "Recipe"
74
+ navbar leading=back actions=[share, more-horizontal]
75
+ image "Photo" height=200
76
+ heading "Tomato soup" level=1
77
+ stack row gap=8
78
+ badge outline "25 min"
79
+ badge outline "Vegan"
80
+ text lines=4
81
+ button primary "Start cooking" fullWidth
82
+ drawer bottom "Options" size=260
83
+ toggle "Metric units" on
84
+ select "Servings" value="4"
85
+ checkbox "Add to shopping list"
86
+ note "Detail screen shown with the options sheet open."
87
+ ```
@@ -0,0 +1,196 @@
1
+ # tsquare components
2
+
3
+ Every component and its props, generated from the catalog. All props are optional unless marked required. "main text" is the prop a quoted string fills.
4
+
5
+ ### Board
6
+ Root canvas (artboard). Holds Screens side by side, plus optional Notes. Must be the root element.
7
+ - title: string (main text)
8
+ - layout: "row" | "grid" — row = all screens side by side; grid = wrap every `columns` screens
9
+ - columns: number
10
+ - gap: number
11
+ - padding: number
12
+ - accent: string — The one UI color: primary buttons, solid badges, checked controls, toggles, active tabs, ghost buttons. blue, indigo, violet, pink, red, orange, green, teal, or a hex color like #1a73e8. Omit for grayscale.
13
+
14
+ ### Screen
15
+ One view of the product in a device frame. Children stack vertically.
16
+ - name: string (main text) — Label shown above the screen
17
+ - device: "phone" | "tablet" | "desktop" | "custom"
18
+ - width: number — Only for device=custom
19
+ - height: number — Only for device=custom
20
+ - chrome: boolean — Phone status bar / desktop browser bar. Default true for phone and desktop.
21
+ - padding: number
22
+ - gap: number
23
+
24
+ ### Note
25
+ Sticky-note annotation. Put it on the Board beside screens, or inside a Screen next to what it explains.
26
+ - text (required): string (main text)
27
+ - color: "yellow" | "blue" | "pink" | "green"
28
+ - width: number
29
+
30
+ ### Stack
31
+ Flex container. The main layout primitive (rows, columns, sidebars, toolbars).
32
+ - direction: "row" | "column"
33
+ - gap: number
34
+ - padding: number
35
+ - align: "start" | "center" | "end" | "stretch"
36
+ - justify: "start" | "center" | "end" | "between" | "around"
37
+ - wrap: boolean
38
+ - grow: boolean — Fill remaining space in the parent
39
+ - width: number | string — Fixed width, e.g. 240 for a sidebar
40
+ - border: "none" | "right" | "left" | "top" | "bottom" | "all"
41
+ - fill: boolean — Light gray background
42
+
43
+ ### Grid
44
+ Equal-width columns that wrap. Good for card grids and galleries.
45
+ - columns (required): number
46
+ - gap: number
47
+ - padding: number
48
+
49
+ ### Card
50
+ Bordered container with optional title. Children stack vertically.
51
+ - title: string (main text)
52
+ - padding: number
53
+ - gap: number
54
+ - variant: "outline" | "filled"
55
+ - grow: boolean
56
+ - width: number | string — Fixed width, e.g. 320. Default fills the space.
57
+
58
+ ### Divider
59
+ Thin separator line.
60
+ - vertical: boolean
61
+
62
+ ### Spacer
63
+ Empty space. Without a size it grows to push siblings apart.
64
+ - size: number — Fixed size in px. Omit to fill remaining space.
65
+
66
+ ### Heading
67
+ Heading text, level 1 (largest) to 3.
68
+ - text (required): string (main text)
69
+ - level: 1 | 2 | 3
70
+ - align: "left" | "center" | "right"
71
+
72
+ ### Text
73
+ Body text, or placeholder lines when `lines` is set and `text` is not.
74
+ - text: string (main text)
75
+ - lines: number — Draw N placeholder lines instead of real text
76
+ - size: "sm" | "md" | "lg"
77
+ - muted: boolean
78
+ - bold: boolean
79
+ - align: "left" | "center" | "right"
80
+
81
+ ### Image
82
+ Image placeholder: a box with an X through it.
83
+ - height: number
84
+ - width: number | string — Default fills the container width
85
+ - label: string (main text)
86
+ - rounded: boolean
87
+
88
+ ### Icon
89
+ Line icon from Lucide.
90
+ - name: string (main text) — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
91
+ - size: number
92
+
93
+ ### Avatar
94
+ Round avatar with initials or a person silhouette.
95
+ - initials: string (main text)
96
+ - size: number
97
+
98
+ ### Badge
99
+ Small pill label for counts or statuses.
100
+ - label (required): string (main text)
101
+ - variant: "solid" | "outline"
102
+ - tone: "neutral" | "success" | "warning" | "danger" — Status color. Default neutral (the accent for solid badges)
103
+
104
+ ### Button
105
+ Button. Primary is filled, secondary is outlined, ghost is text only.
106
+ - label: string (main text)
107
+ - variant: "primary" | "secondary" | "ghost"
108
+ - size: "sm" | "md" | "lg"
109
+ - icon: string — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
110
+ - fullWidth: boolean
111
+
112
+ ### Input
113
+ Text field with optional label. Set multiline for a textarea.
114
+ - label: string (main text)
115
+ - placeholder: string
116
+ - value: string
117
+ - type: "text" | "password" | "search" | "email"
118
+ - multiline: number — Number of rows; 2 or more makes a textarea
119
+ - helper: string
120
+ - error: boolean — Validation error: red border and red helper text
121
+ - grow: boolean — Fill the remaining space in a row
122
+ - width: number | string — Fixed width, e.g. 320. Default fills the space.
123
+
124
+ ### Checkbox
125
+ Checkbox with label.
126
+ - label: string (main text)
127
+ - checked: boolean
128
+
129
+ ### Radio
130
+ Radio button with label.
131
+ - label: string (main text)
132
+ - checked: boolean
133
+
134
+ ### Toggle
135
+ On/off switch with label on the left.
136
+ - label: string (main text)
137
+ - on: boolean
138
+
139
+ ### Select
140
+ Dropdown field (closed state).
141
+ - label: string (main text)
142
+ - value: string
143
+ - placeholder: string
144
+ - width: number | string — Fixed width, e.g. 320. Default fills the space.
145
+
146
+ ### NavBar
147
+ Top app bar. Place first in a Screen.
148
+ - title: string (main text)
149
+ - leading: "none" | "menu" | "back" | "close" | "logo"
150
+ - actions: array of string — Icon names shown on the right
151
+ - align: "left" | "center"
152
+
153
+ ### TabBar
154
+ Bottom tab bar for mobile. Place last in a Screen (before overlays).
155
+ - items (required): array of { label: string, icon: string }
156
+ - active: number
157
+
158
+ ### Tabs
159
+ In-page tabs with an underline on the active one.
160
+ - items (required): array of string
161
+ - active: number
162
+
163
+ ### List
164
+ Vertical list of ListItems.
165
+ - dividers: boolean
166
+ - grow: boolean
167
+
168
+ ### ListItem
169
+ Row in a List. Omit title for a placeholder bar.
170
+ - title: string (main text)
171
+ - subtitle: string
172
+ - leading: "none" | "icon" | "avatar" | "image" | "checkbox"
173
+ - icon: string — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
174
+ - trailing: "none" | "chevron" | "toggle" | "text" | "badge" | "icon"
175
+ - trailingText: string
176
+ - trailingIcon: string — Lucide icon name in kebab-case, e.g. menu, search, arrow-left, settings, bell, user
177
+
178
+ ### Table
179
+ Table with headers. Rows are placeholder bars unless `data` is given.
180
+ - columns (required): array of string
181
+ - rows: number — Placeholder rows when `data` is not given
182
+ - data: array of array of string
183
+
184
+ ### Modal
185
+ Centered dialog over the screen. Must be a direct child of a Screen, listed last.
186
+ - title: string (main text)
187
+ - width: number
188
+ - scrim: boolean — Dim the screen behind. Default true.
189
+
190
+ ### Drawer
191
+ Panel sliding in from an edge: side nav (left), filters/details (right), or bottom sheet. Must be a direct child of a Screen, listed last.
192
+ - side: "left" | "right" | "bottom"
193
+ - title: string (main text)
194
+ - size: number — Width for left/right, height for bottom
195
+ - scrim: boolean — Dim the screen behind. Default true.
196
+ - handle: boolean — Grab handle on bottom sheets. Default true for bottom.