[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

feat(prompts): add showInstructions opt-out (#574)

authored by

paul valladares and committed by
GitHub
(Jun 23, 2026, 11:42 AM -0500) 8f1c3806 06c16c7b

+212 -3
+5
.changeset/fresh-facts-search.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + --- 4 + 5 + Add `showInstructions` option to `select`, `multiselect`, and `groupMultiselect`. Keyboard hints remain shown by default; pass `showInstructions: false` to hide them.
+12 -1
packages/prompts/src/group-multi-select.ts
··· 60 60 * @default 0 61 61 */ 62 62 groupSpacing?: number; 63 + 64 + /** 65 + * Show keyboard instructions below the option list. 66 + * @default true 67 + */ 68 + showInstructions?: boolean; 63 69 } 64 70 65 71 /** ··· 191 197 ); 192 198 }; 193 199 const required = opts.required ?? true; 200 + const showInstructions = opts.showInstructions ?? true; 194 201 195 202 return new GroupMultiSelectPrompt({ 196 203 options: opts.options, ··· 288 295 default: { 289 296 const guidePrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 290 297 const titleLineCount = title.split('\n').length; 291 - const footerLines = formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide); 298 + const footerLines = showInstructions 299 + ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide) 300 + : hasGuide 301 + ? [styleText('cyan', S_BAR_END)] 302 + : []; 292 303 const footerText = footerLines.join('\n'); 293 304 const footerLineCount = footerLines.length + 1; 294 305 const optionsText = limitOptions({
+11 -1
packages/prompts/src/multi-select.ts
··· 27 27 maxItems?: number; 28 28 required?: boolean; 29 29 cursorAt?: Value; 30 + /** 31 + * Show keyboard instructions below the option list. 32 + * @default true 33 + */ 34 + showInstructions?: boolean; 30 35 } 31 36 const computeLabel = (label: string, format: (text: string) => string) => { 32 37 return label ··· 77 82 return `${styleText('dim', S_CHECKBOX_INACTIVE)} ${computeLabel(label, (text) => styleText('dim', text))}`; 78 83 }; 79 84 const required = opts.required ?? true; 85 + const showInstructions = opts.showInstructions ?? true; 80 86 81 87 return new MultiSelectPrompt({ 82 88 options: opts.options, ··· 179 185 default: { 180 186 const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 181 187 const titleLineCount = title.split('\n').length; 182 - const footerLines = formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide); 188 + const footerLines = showInstructions 189 + ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide) 190 + : hasGuide 191 + ? [styleText('cyan', S_BAR_END)] 192 + : []; 183 193 const footerText = footerLines.join('\n'); 184 194 const footerLineCount = footerLines.length + 1; 185 195 return `${title}${prefix}${limitOptions({
+13 -1
packages/prompts/src/select.ts
··· 4 4 type CommonOptions, 5 5 formatInstructionFooter, 6 6 S_BAR, 7 + S_BAR_END, 7 8 S_RADIO_ACTIVE, 8 9 S_RADIO_INACTIVE, 9 10 symbol, ··· 75 76 options: Option<Value>[]; 76 77 initialValue?: Value; 77 78 maxItems?: number; 79 + /** 80 + * Show keyboard instructions below the option list. 81 + * @default true 82 + */ 83 + showInstructions?: boolean; 78 84 } 79 85 80 86 const computeLabel = (label: string, format: (text: string) => string) => { ··· 111 117 } 112 118 }; 113 119 120 + const showInstructions = opts.showInstructions ?? true; 121 + 114 122 return new SelectPrompt({ 115 123 options: opts.options, 116 124 signal: opts.signal, ··· 151 159 default: { 152 160 const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 153 161 const titleLineCount = title.split('\n').length; 154 - const footerLines = formatInstructionFooter(SELECT_INSTRUCTIONS, hasGuide); 162 + const footerLines = showInstructions 163 + ? formatInstructionFooter(SELECT_INSTRUCTIONS, hasGuide) 164 + : hasGuide 165 + ? [styleText('cyan', S_BAR_END)] 166 + : []; 155 167 const footerText = footerLines.join('\n'); 156 168 const footerLineCount = footerLines.length + 1; 157 169 return `${title}${prefix}${limitOptions({
+42
packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap
··· 664 664 ] 665 665 `; 666 666 667 + exports[`groupMultiselect (isCI = false) > showInstructions: false hides instruction footer 1`] = ` 668 + [ 669 + "<cursor.hide>", 670 + "│ 671 + ◆ foo 672 + │ ◻ group1 673 + │ │ ◻ group1value0 674 + │ └ ◻ group1value1 675 + └ 676 + ", 677 + "<cursor.backward count=999><cursor.up count=6>", 678 + "<cursor.down count=1>", 679 + "<erase.down>", 680 + "◇ foo 681 + │", 682 + " 683 + ", 684 + "<cursor.show>", 685 + ] 686 + `; 687 + 667 688 exports[`groupMultiselect (isCI = false) > sliding window loops downwards 1`] = ` 668 689 [ 669 690 "<cursor.hide>", ··· 1625 1646 "<erase.down>", 1626 1647 "◇ foo 1627 1648 │ group1value0, group1value1", 1649 + " 1650 + ", 1651 + "<cursor.show>", 1652 + ] 1653 + `; 1654 + 1655 + exports[`groupMultiselect (isCI = true) > showInstructions: false hides instruction footer 1`] = ` 1656 + [ 1657 + "<cursor.hide>", 1658 + "│ 1659 + ◆ foo 1660 + │ ◻ group1 1661 + │ │ ◻ group1value0 1662 + │ └ ◻ group1value1 1663 + └ 1664 + ", 1665 + "<cursor.backward count=999><cursor.up count=6>", 1666 + "<cursor.down count=1>", 1667 + "<erase.down>", 1668 + "◇ foo 1669 + │", 1628 1670 " 1629 1671 ", 1630 1672 "<cursor.show>",
+40
packages/prompts/test/__snapshots__/multi-select.test.ts.snap
··· 575 575 ] 576 576 `; 577 577 578 + exports[`multiselect (isCI = false) > showInstructions: false hides instruction footer 1`] = ` 579 + [ 580 + "<cursor.hide>", 581 + "│ 582 + ◆ foo 583 + │ ◻ opt0 584 + │ ◻ opt1 585 + └ 586 + ", 587 + "<cursor.backward count=999><cursor.up count=5>", 588 + "<cursor.down count=1>", 589 + "<erase.down>", 590 + "◇ foo 591 + │ none", 592 + " 593 + ", 594 + "<cursor.show>", 595 + ] 596 + `; 597 + 578 598 exports[`multiselect (isCI = false) > shows hints for all selected options 1`] = ` 579 599 [ 580 600 "<cursor.hide>", ··· 1508 1528 "<erase.down>", 1509 1529 "◇ foo 1510 1530 │ opt0", 1531 + " 1532 + ", 1533 + "<cursor.show>", 1534 + ] 1535 + `; 1536 + 1537 + exports[`multiselect (isCI = true) > showInstructions: false hides instruction footer 1`] = ` 1538 + [ 1539 + "<cursor.hide>", 1540 + "│ 1541 + ◆ foo 1542 + │ ◻ opt0 1543 + │ ◻ opt1 1544 + └ 1545 + ", 1546 + "<cursor.backward count=999><cursor.up count=5>", 1547 + "<cursor.down count=1>", 1548 + "<erase.down>", 1549 + "◇ foo 1550 + │ none", 1511 1551 " 1512 1552 ", 1513 1553 "<cursor.show>",
+40
packages/prompts/test/__snapshots__/select.test.ts.snap
··· 461 461 ] 462 462 `; 463 463 464 + exports[`select (isCI = false) > showInstructions: false hides instruction footer 1`] = ` 465 + [ 466 + "<cursor.hide>", 467 + "│ 468 + ◆ foo 469 + │ ● opt0 470 + │ ○ opt1 471 + └ 472 + ", 473 + "<cursor.backward count=999><cursor.up count=5>", 474 + "<cursor.down count=1>", 475 + "<erase.down>", 476 + "◇ foo 477 + │ opt0", 478 + " 479 + ", 480 + "<cursor.show>", 481 + ] 482 + `; 483 + 464 484 exports[`select (isCI = false) > up arrow selects previous option 1`] = ` 465 485 [ 466 486 "<cursor.hide>", ··· 1051 1071 └ 1052 1072 ", 1053 1073 "<cursor.backward count=999><cursor.up count=6>", 1074 + "<cursor.down count=1>", 1075 + "<erase.down>", 1076 + "◇ foo 1077 + │ opt0", 1078 + " 1079 + ", 1080 + "<cursor.show>", 1081 + ] 1082 + `; 1083 + 1084 + exports[`select (isCI = true) > showInstructions: false hides instruction footer 1`] = ` 1085 + [ 1086 + "<cursor.hide>", 1087 + "│ 1088 + ◆ foo 1089 + │ ● opt0 1090 + │ ○ opt1 1091 + └ 1092 + ", 1093 + "<cursor.backward count=999><cursor.up count=5>", 1054 1094 "<cursor.down count=1>", 1055 1095 "<erase.down>", 1056 1096 "◇ foo
+18
packages/prompts/test/group-multi-select.test.ts
··· 481 481 expect(value).toEqual(['group1value0']); 482 482 expect(output.buffer).toMatchSnapshot(); 483 483 }); 484 + 485 + test('showInstructions: false hides instruction footer', async () => { 486 + const result = prompts.groupMultiselect({ 487 + message: 'foo', 488 + input, 489 + output, 490 + showInstructions: false, 491 + required: false, 492 + options: { 493 + group1: [{ value: 'group1value0' }, { value: 'group1value1' }], 494 + }, 495 + }); 496 + 497 + input.emit('keypress', '', { name: 'return' }); 498 + 499 + await result; 500 + expect(output.buffer).toMatchSnapshot(); 501 + }); 484 502 });
+16
packages/prompts/test/multi-select.test.ts
··· 476 476 expect(value).toEqual(['opt6']); 477 477 expect(output.buffer).toMatchSnapshot(); 478 478 }); 479 + 480 + test('showInstructions: false hides instruction footer', async () => { 481 + const result = prompts.multiselect({ 482 + message: 'foo', 483 + options: [{ value: 'opt0' }, { value: 'opt1' }], 484 + showInstructions: false, 485 + required: false, 486 + input, 487 + output, 488 + }); 489 + 490 + input.emit('keypress', '', { name: 'return' }); 491 + 492 + await result; 493 + expect(output.buffer).toMatchSnapshot(); 494 + }); 479 495 });
+15
packages/prompts/test/select.test.ts
··· 377 377 expect(output.buffer).toMatchSnapshot(); 378 378 }); 379 379 380 + test('showInstructions: false hides instruction footer', async () => { 381 + const result = prompts.select({ 382 + message: 'foo', 383 + options: [{ value: 'opt0' }, { value: 'opt1' }], 384 + showInstructions: false, 385 + input, 386 + output, 387 + }); 388 + 389 + input.emit('keypress', '', { name: 'return' }); 390 + 391 + await result; 392 + expect(output.buffer).toMatchSnapshot(); 393 + }); 394 + 380 395 test('correctly limits options with explicit multiline message', async () => { 381 396 output.rows = 12; 382 397