Canvas Design System
Main Site Tokens

Prompt Select

A dropdown that reads as the end of a sentence — "I need help with [Goal…]" — for conversational navigation.

When to Use

Use when: A hero or landing section should route the reader by intent rather than by menu. The phrasing does the work: the prompt sets up a sentence and the dropdown completes it.
Don't use when: It's a form field — use Select, which has proper label, error, and helper wiring. Past six to eight options, a search input or a listing page serves better.

Variants

Default

Brand-bordered dropdown with the serif-italic prompt. The first option doubles as the placeholder, so give it an empty value.

<?= ws_prompt_select('I need help with', 'goalSelect', [
    ''              => 'Goal…',
    'team-building' => 'Team Building',
    'ideation'      => 'Ideation',
]) ?>

Muted

Grey border and subdued prompt, for a secondary instance or one sitting on an already-busy surface.

<?= ws_prompt_select('Filter by', 'filterSelect', $opts, ['variant' => 'muted']) ?>

Responsive

Below 480px the row stacks: the prompt moves above the dropdown at a reduced size, and the dropdown goes full width. Nothing to configure.

States

StateBehavior
DefaultNative <select> with the brand border and a custom chevron.
Focus--focus-ring-width and --focus-ring-offset, the platform's shared focus geometry.
ChangedThe onchange string runs as an inline handler. Because this component navigates rather than collects, that's usually where the routing happens — try it above.
Pre-selectedvalue marks the current option. Useful when the prompt reflects a filter already applied.
DisabledNot supported — there's no disabled option.
ErrorNot supported. Another reason this isn't a form control: there's nowhere to put validation.
<?= ws_prompt_select('Most popular', 'popularSelect', $opts, [
    'onchange' => 'goToPopular(this)',
]) ?>

Real-World Usage

The homepage hero's intent router: goals come from the database, and selecting one navigates straight to that goal's collection. Note the aria-label passed through to give the select an accessible name — see Accessibility for why that's necessary.

<?= ws_prompt_select('I need help with', 'heroGoal', $goalOptions, [
    'onchange' => 'if (this.value) location.href = "/library/goals/" + this.value;',
]) ?>

<script>
// The prompt is a <span>, so name the control explicitly.
document.getElementById('heroGoal')
        .setAttribute('aria-label', 'I need help with');
</script>

Options

OptionTypeDefaultPurpose
$labelstring''First positional. The prompt, rendered serif-italic. Write it so the option completes the sentence.
$namestring''Second positional. Field name and default ID.
$selectOptionsarray[]Third positional. Flat value => label pairs, same shape as Select.
valuestring''Pre-selected value
onchangestring''Inline JS handler, typically the navigation
idstring$nameElement ID
classstring''Additional CSS classes on the wrapper
variantstring'default'default | muted

There's no attrs option, so attributes can't be passed to the select directly — set them from JS using the id.

Accessibility

ConcernBehavior
SemanticsA native <select>, so keyboard operation, type-ahead, and the mobile picker are all native.
LabellingMissing. The prompt renders as <span class="ws-prompt-select__label">, not a <label for>, and no aria-label is emitted. The select therefore has no accessible name — a screen reader announces an unnamed combo box. Set aria-label from JS using the element's id, as shown above.
Click targetBecause the prompt isn't a real label, clicking it doesn't open the dropdown — a usability loss for everyone.
KeyboardTab to reach, arrows or typing to change. Standard.
Navigate on changeChanging the value navigates away. Keyboard users move through options with arrows, which fires change on each one — on some browsers that means navigating before they reach their choice. Prefer a Go button, or navigate on blur instead.
Placeholder optionGive the first option an empty value so the handler can guard against it, as in the example.
ContrastDefault and muted variants both pair tested border and text tokens.
warning Two fixes worth making in the template: render the prompt as <label for="{id}"> rather than a span (which solves naming and click-to-open at once), and accept an attrs option so callers can pass ARIA without JavaScript.

Tokens

TokenUsed for
--color-primaryDefault variant border
--gray-200Muted variant border
--bg-surfaceSelect background
--color-ink / --color-ink-mutedPrompt and muted-prompt colour
--focus-ring-width / --focus-ring-offsetFocus ring geometry
--text-body / --text-uiPrompt and option type scale

CSS Classes

ClassPurpose
.ws-prompt-selectFlex wrapper, stacking below 480px
.ws-prompt-select--mutedMuted variant
.ws-prompt-select__labelSerif-italic prompt (a <span> — see Accessibility)
.ws-prompt-select__dropdownPositioning context for the select and chevron
.ws-prompt-select__selectThe native <select>

Files

FilePurpose
includes/components/helpers.phpws_prompt_select() helper function
includes/components/prompt-select.phpTemplate — prompt, select, and chevron markup
includes/components/components.cssStyles (.ws-prompt-select rules)