Select

Native <select> repainted with appearance: none, a chevron caret inlined as an SVG data URL, and reserved padding on the caret edge. [multiple] drops the caret and unlocks vertical sizing. The caret position flips on :dir(rtl) because background-position has no logical-axis keyword.

Live

Single select

<select>
<option>Designer</option>
<option>Engineer</option>
<option>Product manager</option>
</select>
@use '@adnap/krysalicss' with (
  $feature-list: ('base-reset', 'base-global', 'element-select')
);
Playground

Multi-select (caret removed, vertical sizing)

<select multiple size="3">
<option selected>Frontend</option>
<option>Backend</option>
<option selected>Design</option>
</select>
@use '@adnap/krysalicss' with (
  $feature-list: ('base-reset', 'base-global', 'element-select')
);
Playground

Markup

markup
<select>
  <option>Designer</option>
  <option>Engineer</option>
  <option>Product manager</option>
</select>

<select multiple size="3">
  <option selected>Frontend</option>
  <option>Backend</option>
  <option selected>Design</option>
</select>

When to use

  • One-of-many choices with five or more options. For two to four options, Radio typically wins on scan-ability.
  • For multi-select short lists, prefer a checkbox group; reserve [multiple] for long lists where a multi-select listbox makes sense.
  • For typeahead / search-as-you-type, the framework doesn't ship a combobox; lift to a dedicated component.

Variables

VariableDefaultNotes
$selector'select'Element selector.
$padding-y$size-extra-small (0.5rem)Vertical padding.
$padding-x$size-small (0.75rem)Inline-start padding. Also applies to inline-end under [multiple].
$padding-x-with-caret2remInline-end padding reserved for the caret SVG so option text never collides with it.
$border-radius$global-border-radius (5px)Outer corner radius.
$border-width$global-border-width (1px)Stroke. Mixed at $border-mix against currentColor.
$focus-outline-width$global-focus-outline-width (2px)Focus-visible ring width.
$focus-outline-style$global-focus-outline-style (solid)Focus ring style.
$focus-outline-offset$global-focus-outline-offset (2px)Gap between border and ring.
$disabled-opacity$global-disabled-opacity (0.6)Routed through controls.disabledControl().
$border-mix$global-border-mix (50%)Border tint vs. currentColor.
$caret-imageInline SVG chevron (data URL, stroke #888)Painted via background-image. Override the SCSS variable to swap the glyph or pin its stroke to a theme token.

Override example

app.scss
@use '@adnap/krysalicss/element/select' with (
  $padding-x-with-caret: 2.5rem,
  $border-radius: 8px,
);

Tokens consumed

TokenUsed for
--kc-input-bg / --kc-input-fgSurface fill and text colour. Optional: falls back to transparent + currentColor.
--kc-fgFocused border colour and focus-ring fallback.
--kc-focus-ringPainted on :focus-visible. Falls back to --kc-fg then currentColor.
--kc-danger-bgBorder tint when the control matches :user-invalid or [aria-invalid="true"].

Accessibility

  • Anchored on native <select>: keyboard navigation (Up / Down / type-ahead), AT semantics, and mobile-OS-native pickers ship for free.
  • Always pair with a <label> (visible or aria-labelledby).
  • The caret is decorative; option-list state is conveyed by the native widget. The visual cue uses #888 stroke today — adequate against the shipped themes but consumers retuning $caret-image for a custom palette must re-verify WCAG 1.4.11 (3:1).
  • Focus ring uses --kc-focus-ring with a fallback chain into --kc-fg / currentColor; the focused border also swaps to --kc-fg for redundant non-text contrast.
  • Under RTL, the caret position flips via the :dir(rtl) companion rule (when $global-rtl: true).

See also

  • Radio — one-of-many choice when the option count is small (≤ 5) and visibility matters.
  • Text input — single-line text input with the same chrome.
  • Forms overview — full form-control roster.
  • Field wrapper — pairs every form control with label + help text.