···11+/**
22+ * Builds `public/snapshot`: a serialized node_modules tree for StackBlitz WebContainer.
33+ * The docs site mounts this in the browser so live examples can `import` Clack without
44+ * running `npm install` inside the VM. Versions come from the repo root package.json
55+ * so playground code matches Twoslash / the docs.
66+ */
17import fs from 'node:fs/promises';
28import { fileURLToPath } from 'node:url';
39import { snapshot } from '@webcontainer/snapshot';
44-import {x} from 'tinyexec';
55-import { createHash } from "node:crypto";
1010+import { x } from 'tinyexec';
1111+import { createHash } from 'node:crypto';
1212+1313+const rootDir = new URL('../', import.meta.url);
61477-const PACKAGE_JSON = {
88- name: 'example',
99- type: 'module',
1010- version: '0.0.0',
1111- dependencies: {
1212- "@bomb.sh/args": "latest",
1313- "@clack/core": "1.0.0-alpha.0",
1414- "@clack/prompts": "1.0.0-alpha.0"
1515- }
1515+async function packageJsonForSnapshot() {
1616+ const raw = await fs.readFile(new URL('package.json', rootDir), 'utf8');
1717+ const root = JSON.parse(raw) as {
1818+ dependencies: Record<string, string>;
1919+ };
2020+ const { dependencies: d } = root;
2121+ return {
2222+ name: 'example',
2323+ type: 'module',
2424+ version: '0.0.0',
2525+ dependencies: {
2626+ '@bomb.sh/args': d['@bomb.sh/args'],
2727+ '@clack/core': d['@clack/core'],
2828+ '@clack/prompts': d['@clack/prompts'],
2929+ },
3030+ };
1631}
1732const IGNORE_FILES = ['*.md', '*.d.*', '*.map', 'LICENSE', 'license'];
1818-const rootDir = new URL('../', import.meta.url);
1933const snapshotDir = new URL(`./snapshot-${hash()}/`, rootDir);
2034const outFile = new URL('./public/snapshot', rootDir);
21352236async function run() {
3737+ const pkg = await packageJsonForSnapshot();
2338 await fs.mkdir(snapshotDir, { recursive: true });
2424- await fs.writeFile(new URL('package.json', snapshotDir), JSON.stringify(PACKAGE_JSON));
3939+ await fs.writeFile(new URL('package.json', snapshotDir), JSON.stringify(pkg));
2540 await x('npm', ['install'], {
2641 nodeOptions: {
2742 cwd: fileURLToPath(snapshotDir),
+3
src/content/docs/clack/basics/getting-started.mdx
···9090- `text()` - For text input with validation
9191- `password()` - For secure password input with masking
9292- `select()` - For selection menus
9393+- `selectKey()` - For choosing an option by pressing its key
9394- `confirm()` - For yes/no confirmations
9495- `multiselect()` - For multiple selections
9596- `groupMultiselect()` - For grouped multiple selections
9697- `autocomplete()` - For searchable selection menus
9798- `path()` - For file/directory path selection with autocomplete
9999+- `date()` - For structured date entry (YMD / MDY / DMY)
98100- `note()` - For displaying information
99101- `box()` - For boxed text display
100102- `spinner()` - For loading states
101103- `progress()` - For progress bar display
102104- `tasks()` - For sequential task execution
103105- `taskLog()` - For log output that clears on success
106106+- `stream` - For multi-line streamed output (async iterators, readable streams)
104107105108### Low-Level Primitives
106109
+75
src/content/docs/clack/guides/examples.mdx
···745745746746## New Features Examples
747747748748+### Date picker
749749+750750+Use `date` with `format` (`YMD`, `MDY`, or `DMY`) and optional bounds:
751751+752752+```ts twoslash
753753+import { date, isCancel, cancel, outro } from '@clack/prompts';
754754+755755+async function pickReleaseDate() {
756756+ const result = await date({
757757+ message: 'Pick a release date',
758758+ format: 'YMD',
759759+ minDate: new Date('2026-01-01'),
760760+ maxDate: new Date('2026-12-31'),
761761+ });
762762+763763+ if (isCancel(result)) {
764764+ cancel('Operation cancelled.');
765765+ process.exit(0);
766766+ }
767767+768768+ outro(`Selected: ${result.toISOString().slice(0, 10)}`);
769769+}
770770+```
771771+772772+### Select by key
773773+774774+Compact menus where each choice is bound to a key:
775775+776776+```ts twoslash
777777+import { selectKey, isCancel } from '@clack/prompts';
778778+779779+async function confirmDestructive() {
780780+ const choice = await selectKey({
781781+ message: 'Delete all cache?',
782782+ options: [
783783+ { value: 'y', label: 'Yes, delete' },
784784+ { value: 'n', label: 'No, keep' },
785785+ ],
786786+ });
787787+788788+ if (isCancel(choice) || choice === 'n') {
789789+ return false;
790790+ }
791791+ return choice === 'y';
792792+}
793793+```
794794+748795### Path Selection
749796750797The `path` prompt provides file system navigation with autocomplete:
···756803 const configPath = await path({
757804 message: 'Select a configuration file:',
758805 root: process.cwd(),
806806+ directory: false,
759807 validate: (value) => {
760808 if (!value?.endsWith('.json') && !value?.endsWith('.yaml')) {
761809 return 'Please select a .json or .yaml file';
···770818 }
771819772820 console.log(`Selected config: ${configPath}`);
821821+}
822822+```
823823+824824+With `directory: true`, only folders appear in suggestions; with an existing directory as `initialValue`, **Enter** submits that folder directly (v1.2.0).
825825+826826+### Autocomplete Tab and placeholder
827827+828828+When the search box is empty and `placeholder` is set, **Tab** inserts the placeholder string—useful for default tokens:
829829+830830+```ts twoslash
831831+import { autocomplete, isCancel } from '@clack/prompts';
832832+833833+async function pickWithDefaultQuery() {
834834+ const tag = await autocomplete({
835835+ message: 'Filter by tag:',
836836+ placeholder: 'latest',
837837+ options: [
838838+ { value: 'latest', label: 'latest' },
839839+ { value: 'stable', label: 'stable' },
840840+ { value: 'canary', label: 'canary' },
841841+ ],
842842+ });
843843+844844+ if (isCancel(tag)) {
845845+ return null;
846846+ }
847847+ return tag;
773848}
774849```
775850
+32-5
src/content/docs/clack/packages/core.mdx
···4343Then import the components you need:
44444545```ts twoslash
4646-import {
4646+import {
4747 // Prompt classes
4848- TextPrompt,
4949- SelectPrompt,
4848+ TextPrompt,
4949+ SelectPrompt,
5050 ConfirmPrompt,
5151 PasswordPrompt,
5252 MultiSelectPrompt,
5353 GroupMultiSelectPrompt,
5454 SelectKeyPrompt,
5555 AutocompletePrompt,
5656+ DatePrompt,
5757+5858+ // Layout helpers
5959+ block,
6060+ getColumns,
6161+ getRows,
6262+ wrapTextWithPrefix,
56635764 // Utilities
5865 isCancel,
···6875 type GroupMultiSelectOptions,
6976 type SelectKeyOptions,
7077 type AutocompleteOptions,
7878+ type DateFormat,
7979+ type DateOptions,
8080+ type DateParts,
7181} from '@clack/core';
7282```
7383···144154 - Supports validation
145155 - Placeholder text
146156 - Initial value
147147- - Separate `userInput` and `value` tracking
157157+ - Separate `userInput` and `value` tracking; use **`userInputWithCursor`** when rendering so the raw buffer and cursor position match what the user sees
1481581491592. **SelectPrompt**: For selection from options
150160 - Custom rendering
···1581681591694. **AutocompletePrompt**: For searchable selection
160170 - Type-ahead filtering
161161- - Custom filtering logic
171171+ - Custom filtering logic via the `filter` option
162172 - Multiple selection support (`multiple: true`)
163173 - Dynamic options (function or array)
174174+ - As of v1.2.0, the built-in default filter runs only when `filter` is set explicitly **or** when `options` is not a getter—so lazy `options` getters are not pre-filtered unexpectedly
1641751651765. **PasswordPrompt**: For secure input
166177 - Character masking
···1831948. **SelectKeyPrompt**: For key-based selection
184195 - Custom key bindings
185196 - Multiple selection support
197197+198198+9. **DatePrompt**: For structured date entry
199199+ - Segment-based editing (year, month, day) with `DateFormat` (`YMD`, `MDY`, or `DMY`)
200200+ - Optional `locale` for segment order and separator via `Intl`
201201+ - Optional `separator` override for display
202202+ - `minDate` / `maxDate` bounds and `DateParts` segment values
203203+204204+10. **Multi-line input**: v1.2.0 does not include a dedicated multi-line prompt in `@clack/core` or `@clack/prompts`. Use `text()` for a single line, an external editor or stdin for paragraphs, or extend `Prompt` for custom multi-line TTY behavior.
205205+206206+## Layout utilities
207207+208208+These helpers are useful when building custom prompts or lists that respect terminal width:
209209+210210+- **`getColumns(output?)` / `getRows(output?)`**: Terminal size for the given writable (defaults to `stdout`).
211211+- **`wrapTextWithPrefix(output, text, prefix, ...)`**: Wrap lines to the terminal width while repeating a prefix on each line (used heavily by prompts for guide-aligned output).
212212+- **`block`**: Lower-level TTY helper (raw mode, keypress handling, optional cursor hide) used when building custom full-screen or overlay flows; most apps use the prompt classes instead.
186213187214## Global Settings
188215
+138-6
src/content/docs/clack/packages/prompts.mdx
···48484949### Guide Lines
50505151-The `withGuide` option controls whether the default Clack border/guide lines are displayed. You can disable them globally or per-prompt:
5151+The `withGuide` option (boolean **option**, not a separate API) turns Clack’s border/guide lines on or off. Every prompt accepts it alongside `message` and friends. You can set it globally with `updateSettings` or override it per call.
52525353```ts twoslash
5454import { text, updateSettings } from '@clack/prompts';
···6262 withGuide: false, // Disable guide lines for this prompt
6363});
6464```
6565+6666+Session helpers use the same option on their **second argument**: `intro(title, { withGuide: false })`, `outro(message, { withGuide: false })`, and `cancel(message, { withGuide: false })`.
6767+6868+`autocomplete` and `multiselect` respect `withGuide: false` per prompt (as of v1.2.0), matching other prompts.
65696670### AbortController Support
6771···245249<font color="#06989A">│</font> ◻ SvelteKit (Compile-time framework)
246250<font color="#06989A">└</font></pre>
247251252252+### Select by key
253253+254254+`selectKey` shows each option with a visible key (the option `value`, typically one character). The user presses that key instead of moving a cursor with arrows—useful for compact yes/no/maybe menus or vim-style shortcuts.
255255+256256+```ts twoslash
257257+import { selectKey, isCancel } from '@clack/prompts';
258258+259259+const action = await selectKey({
260260+ message: 'What next?',
261261+ options: [
262262+ { value: 'y', label: 'Continue' },
263263+ { value: 'n', label: 'Stop' },
264264+ { value: 's', label: 'Skip', hint: 'optional' },
265265+ ],
266266+ caseSensitive: false,
267267+});
268268+269269+if (isCancel(action)) {
270270+ process.exit(0);
271271+}
272272+```
273273+248274### Autocomplete
249275250276The `autocomplete` prompt combines text input with a searchable list of options. It's perfect for when you have a large list of options and want to help users find what they're looking for quickly.
···274300<font color="#06989A">│</font> ○ Nuxt (Vue framework)
275301<font color="#06989A">└</font></pre>
276302303303+If you set `placeholder` and the search field is **empty**, pressing **Tab** copies the placeholder string into the input (v1.2.0)—handy for default search tokens or quick acceptance of a suggested value.
304304+305305+#### Dynamic options (getter)
306306+307307+Instead of a static array, `options` can be a **function** whose `this` is the underlying [`AutocompletePrompt`](https://github.com/bombshell-dev/clack/blob/main/packages/core/src/prompts/autocomplete.ts) from `@clack/core`. The function runs again whenever the search text changes, so you can read **`this.userInput`** and return a **new array in display order**—for example closest / highest-score matches first (similar to [fzf](https://github.com/junegunn/fzf)-style UIs). This pattern is what [issue #467](https://github.com/bombshell-dev/clack/issues/467) discusses for custom ranking and libraries like [Fuse.js](https://fusejs.io/).
308308+309309+The high-level `autocomplete` wrapper still applies its **default `filter`** (substring match on label, hint, and value) to whatever your getter returns. If you already narrow or rank items in the getter, disable that second pass with **`filter: (_search, _option) => true`**, or pass a custom `(search, option) => boolean` aligned with your getter.
310310+311311+`options` as a getter must be **synchronous**; there is no async API here—preload or sync work inside the function.
312312+313313+The same `options` shape is supported on **`autocompleteMultiselect`**.
314314+315315+```ts twoslash
316316+import { autocomplete } from '@clack/prompts';
317317+import type { AutocompletePrompt } from '@clack/core';
318318+import type { Option } from '@clack/prompts';
319319+320320+const pool: Option<string>[] = [
321321+ { value: 'next', label: 'Next.js', hint: 'React' },
322322+ { value: 'nuxt', label: 'Nuxt', hint: 'Vue' },
323323+ { value: 'nest', label: 'NestJS', hint: 'Node' },
324324+];
325325+326326+function rankByQuery(query: string, items: Option<string>[]): Option<string>[] {
327327+ const q = query.trim().toLowerCase();
328328+ if (!q) return [...items];
329329+ return [...items]
330330+ .filter((o) => {
331331+ const t = `${o.label ?? ''} ${o.hint ?? ''} ${o.value}`.toLowerCase();
332332+ return t.includes(q);
333333+ })
334334+ .sort((a, b) => {
335335+ const la = (a.label ?? '').toLowerCase();
336336+ const lb = (b.label ?? '').toLowerCase();
337337+ const sa = la.startsWith(q) ? 0 : 1;
338338+ const sb = lb.startsWith(q) ? 0 : 1;
339339+ return sa - sb || la.localeCompare(lb);
340340+ });
341341+}
342342+343343+const picked = await autocomplete({
344344+ message: 'Pick a framework',
345345+ options(this: AutocompletePrompt<Option<string>>) {
346346+ return rankByQuery(this.userInput, pool);
347347+ },
348348+ filter: (_search, _option) => true,
349349+});
350350+```
351351+277352### Autocomplete Multiselect
278353279354The `autocompleteMultiselect` combines the search functionality of autocomplete with the ability to select multiple options.
···329404Options:
330405- `message`: The prompt message to display
331406- `root`: The starting directory for path suggestions (defaults to current working directory)
332332-- `directory`: When `true`, only directories are shown (defaults to `false`)
333333-- `initialValue`: Pre-fill the path input with an initial value
407407+- `directory`: When `true`, only **directories** appear in suggestions while you navigate; you still move through the tree normally (v1.2.0 fixes for directory-only mode).
408408+- `initialValue`: Pre-fill the path input. In **directory** mode, if `initialValue` already points at an existing directory, pressing **Enter** submits **that** directory immediately instead of jumping to the first child (v1.2.0).
334409- `validate`: Custom validation function for the selected path
335410411411+### Date input
412412+413413+`date` returns a `Date` on success or a cancel symbol. Use `format` to pick segment order: `YMD`, `MDY`, or `DMY`. Pass `locale` (BCP 47 string) to derive order and separators from `Intl`, or rely on the format alone.
414414+415415+```ts twoslash
416416+import { date, isCancel } from '@clack/prompts';
417417+418418+const picked = await date({
419419+ message: 'Pick a date',
420420+ format: 'YMD',
421421+ minDate: new Date('2026-01-01'),
422422+ maxDate: new Date('2026-12-31'),
423423+});
424424+425425+if (isCancel(picked)) {
426426+ process.exit(0);
427427+}
428428+429429+// picked is a Date
430430+```
431431+432432+Options include `defaultValue`, `initialValue`, `minDate`, `maxDate`, `validate`, and the usual `signal`, `input`, `output`, and `withGuide` settings.
433433+336434### Confirmation
337435338436```ts twoslash
···347445<font color="#06989A">◆</font> Do you want to continue?
348446<font color="#06989A">│</font> <font color="#4E9A06">●</font> Yes / ○ No
349447<font color="#06989A">└</font></pre>
448448+449449+Options:
450450+- `vertical: true`: Stack Yes / No vertically instead of inline (v1.0.1+).
451451+- Multi-line `message` strings wrap correctly; guide lines apply to wrapped confirmation text (v1.2.0).
350452351453## Grouping
352454···866968The prompts package supports internationalization through the `updateSettings` function. You can customize the messages used by various prompts to match your preferred language.
867969868970```ts twoslash
869869-// @errors: 2353
870971import { updateSettings, select, cancel } from '@clack/prompts';
871972872973// Update global messages
873974updateSettings({
874975 messages: {
875976 cancel: "Operación cancelada",
876876- error: "Se ha producido un error"
877877- }
977977+ error: "Se ha producido un error",
978978+ },
979979+ date: {
980980+ monthNames: [
981981+ "enero", "febrero", "marzo", "abril", "mayo", "junio",
982982+ "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre",
983983+ ],
984984+ messages: {
985985+ required: "Introduce una fecha válida",
986986+ invalidMonth: "Solo hay 12 meses",
987987+ invalidDay: (days, month) => `Solo hay ${days} días en ${month}`,
988988+ afterMin: (min) => `La fecha debe ser el ${min.toISOString().slice(0, 10)} o posterior`,
989989+ beforeMax: (max) => `La fecha debe ser el ${max.toISOString().slice(0, 10)} o anterior`,
990990+ },
991991+ },
878992});
879993880994// Use the select prompt with translated content
···9611075<font color="#4E9A06">◇</font> Job1{`...`} <font color="#4E9A06">done</font>
9621076<font color="#555753">│</font> Job2{`...`} <font color="#4E9A06">done</font>
9631077<font color="#555753">│</font> Job3{`...`} <font color="#4E9A06">done</font></pre>
10781078+10791079+### limitOptions
10801080+10811081+`limitOptions` trims a long option list to what fits the terminal, returning the lines to render and keeping the active index visible—intended for **custom** prompts that mirror Clack’s sliding window (see `@clack/prompts` source). Pass `options`, `cursor`, a `style` callback, optional `maxItems`, `columnPadding`, `rowPadding`, and `output` if not using `stdout`.
10821082+10831083+```ts twoslash
10841084+import { limitOptions } from '@clack/prompts';
10851085+import { styleText } from 'node:util';
10861086+10871087+const options = ['apple', 'banana', 'cherry', 'date'];
10881088+const lines = limitOptions({
10891089+ options,
10901090+ cursor: 2,
10911091+ style: (opt, active) =>
10921092+ active ? styleText('cyan', opt) : styleText('dim', opt),
10931093+ maxItems: 8,
10941094+});
10951095+```