[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: add `withGuide` support to selectKey prompt (#452)

authored by

James Garbutt and committed by
GitHub
(Feb 4, 2026, 9:19 AM UTC) 76550d6f 86e36d8c

+125 -7
+5
.changeset/modern-dots-make.md
··· 1 + --- 2 + "@clack/prompts": patch 3 + --- 4 + 5 + Add `withGuide` support to selectKey prompt
+9 -7
packages/prompts/src/select-key.ts
··· 1 - import { SelectKeyPrompt, wrapTextWithPrefix } from '@clack/core'; 1 + import { SelectKeyPrompt, settings, wrapTextWithPrefix } from '@clack/core'; 2 2 import color from 'picocolors'; 3 3 import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; 4 4 import type { Option } from './select.js'; ··· 40 40 initialValue: opts.initialValue, 41 41 caseSensitive: opts.caseSensitive, 42 42 render() { 43 - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; 43 + const hasGuide = (opts.withGuide ?? settings.withGuide) !== false; 44 + const title = `${hasGuide ? `${color.gray(S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 44 45 45 46 switch (this.state) { 46 47 case 'submit': { 47 - const submitPrefix = `${color.gray(S_BAR)} `; 48 + const submitPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 48 49 const selectedOption = 49 50 this.options.find((opt) => opt.value === this.value) ?? opts.options[0]; 50 51 const wrapped = wrapTextWithPrefix( ··· 55 56 return `${title}${wrapped}`; 56 57 } 57 58 case 'cancel': { 58 - const cancelPrefix = `${color.gray(S_BAR)} `; 59 + const cancelPrefix = hasGuide ? `${color.gray(S_BAR)} ` : ''; 59 60 const wrapped = wrapTextWithPrefix( 60 61 opts.output, 61 62 opt(this.options[0], 'cancelled'), 62 63 cancelPrefix 63 64 ); 64 - return `${title}${wrapped}\n${color.gray(S_BAR)}`; 65 + return `${title}${wrapped}${hasGuide ? `\n${color.gray(S_BAR)}` : ''}`; 65 66 } 66 67 default: { 67 - const defaultPrefix = `${color.cyan(S_BAR)} `; 68 + const defaultPrefix = hasGuide ? `${color.cyan(S_BAR)} ` : ''; 69 + const defaultPrefixEnd = hasGuide ? color.cyan(S_BAR_END) : ''; 68 70 const wrapped = this.options 69 71 .map((option, i) => 70 72 wrapTextWithPrefix( ··· 74 76 ) 75 77 ) 76 78 .join('\n'); 77 - return `${title}${wrapped}\n${color.cyan(S_BAR_END)}\n`; 79 + return `${title}${wrapped}\n${defaultPrefixEnd}\n`; 78 80 } 79 81 } 80 82 },
+72
packages/prompts/test/__snapshots__/select-key.test.ts.snap
··· 63 63 ] 64 64 `; 65 65 66 + exports[`text (isCI = false) > global withGuide: false removes guide 1`] = ` 67 + [ 68 + "<cursor.hide>", 69 + "◆ foo 70 +  a  Option A 71 +  b  Option B 72 + 73 + ", 74 + "<cursor.show>", 75 + "<cursor.backward count=999><cursor.up count=4>", 76 + "<erase.down>", 77 + "◇ foo 78 + Option A", 79 + " 80 + ", 81 + ] 82 + `; 83 + 66 84 exports[`text (isCI = false) > input is case-insensitive by default 1`] = ` 67 85 [ 68 86 "<cursor.hide>", ··· 265 283 "<erase.down>", 266 284 "◇ foo 267 285 │ Option B", 286 + " 287 + ", 288 + ] 289 + `; 290 + 291 + exports[`text (isCI = false) > withGuide: false removes guide 1`] = ` 292 + [ 293 + "<cursor.hide>", 294 + "◆ foo 295 +  a  Option A 296 +  b  Option B 297 + 298 + ", 299 + "<cursor.show>", 300 + "<cursor.backward count=999><cursor.up count=4>", 301 + "<erase.down>", 302 + "◇ foo 303 + Option A", 268 304 " 269 305 ", 270 306 ] ··· 333 369 ] 334 370 `; 335 371 372 + exports[`text (isCI = true) > global withGuide: false removes guide 1`] = ` 373 + [ 374 + "<cursor.hide>", 375 + "◆ foo 376 +  a  Option A 377 +  b  Option B 378 + 379 + ", 380 + "<cursor.show>", 381 + "<cursor.backward count=999><cursor.up count=4>", 382 + "<erase.down>", 383 + "◇ foo 384 + Option A", 385 + " 386 + ", 387 + ] 388 + `; 389 + 336 390 exports[`text (isCI = true) > input is case-insensitive by default 1`] = ` 337 391 [ 338 392 "<cursor.hide>", ··· 539 593 ", 540 594 ] 541 595 `; 596 + 597 + exports[`text (isCI = true) > withGuide: false removes guide 1`] = ` 598 + [ 599 + "<cursor.hide>", 600 + "◆ foo 601 +  a  Option A 602 +  b  Option B 603 + 604 + ", 605 + "<cursor.show>", 606 + "<cursor.backward count=999><cursor.up count=4>", 607 + "<erase.down>", 608 + "◇ foo 609 + Option A", 610 + " 611 + ", 612 + ] 613 + `;
+39
packages/prompts/test/select-key.test.ts
··· 211 211 expect(output.buffer).toMatchSnapshot(); 212 212 }); 213 213 214 + test('withGuide: false removes guide', async () => { 215 + const result = prompts.selectKey({ 216 + message: 'foo', 217 + options: [ 218 + { label: 'Option A', value: 'a' }, 219 + { label: 'Option B', value: 'b' }, 220 + ], 221 + withGuide: false, 222 + input, 223 + output, 224 + }); 225 + 226 + input.emit('keypress', 'a', { name: 'a' }); 227 + 228 + await result; 229 + 230 + expect(output.buffer).toMatchSnapshot(); 231 + }); 232 + 233 + test('global withGuide: false removes guide', async () => { 234 + updateSettings({ withGuide: false }); 235 + 236 + const result = prompts.selectKey({ 237 + message: 'foo', 238 + options: [ 239 + { label: 'Option A', value: 'a' }, 240 + { label: 'Option B', value: 'b' }, 241 + ], 242 + input, 243 + output, 244 + }); 245 + 246 + input.emit('keypress', 'a', { name: 'a' }); 247 + 248 + await result; 249 + 250 + expect(output.buffer).toMatchSnapshot(); 251 + }); 252 + 214 253 test('long submitted labels are wrapped correctly', async () => { 215 254 output.columns = 40; 216 255