Native Select

Multiple List

A multiple native select with the list already open and a note about the keys: without an explanation of Ctrl this control does not work.

  • select
  • multiple
  • native
  • keyboard

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/nativeselect-004?lang=en

Ctrl / Several with the modifier held, a range with Shift.

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "nativeselect-004" (Multiple List) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/nativeselect-004.json

Registry item: https://vibeui.ru/r/nativeselect-004.json
Installs to: components/vibeui/nativeselect-004.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A multiple native select with the list already open and a note about the keys: without an explanation of Ctrl this control does not work.

A multiple native select: the list stays open and the selection keys are named right below it. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Nativeselect004 } from "@/components/vibeui/nativeselect-004"

<Nativeselect004
  label="Interface languages"
  hint="Several with the modifier held, a range with Shift."
  rows={5}
/>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-nativeselect-004-* palette — do not swap it for your theme tokens
- the size attribute: a collapsed multiple field shows one row and hides how much is selected
- the note about the keys: almost nobody guesses Ctrl and Shift, and the choice silently becomes single
- the <kbd> markup for keys — it carries the meaning when read aloud too
- the native field as a whole: on a phone the system opens its own checkable list, a hand-built menu does not fit there
- the block's own light surface: without it the dark label disappears on a dark background

## 6. You may change
- the field name through label, the key note through hint, the choices through options and the list height through rows
- the selection color through accent
- the highlight of a chosen row through option:checked
- the initial selection through defaultValue

## 7. Rules
- A multiple select is the weakest native control: past seven options a list of checkboxes is more honest.
- One stray mouse click wipes the whole selection — warn about it or persist the choice until submit.
- option:checked is not styleable everywhere: on some platforms the system paints the highlight and keeps its own color.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/nativeselect-004.json
https://vibeui.ru/r/nativeselect-004.json

Компонент самодостаточен: один файл, без зависимостей, палитра в локальных переменных --vibeui-nativeselect-004-*. Серверный: из хуков только useId, состояние держит сам select с атрибутом multiple. Список раскрыт заранее атрибутом size (проп rows): закрытое множественное поле показывает лишь первую строку и врёт про количество выбранного. Под полем стоит строка с <kbd>, прямо называющая клавиши, — иначе про Ctrl и Shift никто не догадывается. appearance:none здесь не нужен: у раскрытого списка нет стрелки, поэтому рамка и скругление задаются напрямую. Выбранные пункты подсвечиваются правилом option:checked, но глубина стилизации зависит от платформы.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import { useId } from "react"
import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Nativeselect004Props = Omit<
  ComponentPropsWithoutRef<"select">,
  "children" | "size" | "multiple"
> & {
  label?: string
  hint?: string
  options?: string[]
  rows?: number
  accent?: string
}

// Идея компонента: множественный select без объяснения не работает — с
// мышью нужен Ctrl, с клавиатуры Shift, и об этом никто не догадывается.
// Поэтому список раскрыт заранее (атрибут size), а под ним стоит строка,
// прямо называющая клавиши. Раскрытый список честнее закрытого: видно,
// что выбирать можно несколько.
const STYLES = `
:where([data-vibeui-block="nativeselect-004"]){
--vibeui-nativeselect-004-surface:oklch(1 0 0);
--vibeui-nativeselect-004-surface-border:oklch(0.91 0.006 265);
--vibeui-nativeselect-004-fg:oklch(0.24 0.016 265);
--vibeui-nativeselect-004-muted:oklch(0.54 0.014 265);
--vibeui-nativeselect-004-field-border:oklch(0.85 0.01 265);
--vibeui-nativeselect-004-accent:oklch(0.55 0.2 262);
--vibeui-nativeselect-004-key-bg:oklch(0.96 0.004 265);
--vibeui-nativeselect-004-radius:0.625rem;
--vibeui-nativeselect-004-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="nativeselect-004"]{
box-sizing:border-box;width:100%;max-width:22rem;
padding:1rem;border-radius:0.875rem;
background:var(--vibeui-nativeselect-004-surface);
border:1px solid var(--vibeui-nativeselect-004-surface-border);
font-family:var(--vibeui-nativeselect-004-font);color:var(--vibeui-nativeselect-004-fg);
display:flex;flex-direction:column;gap:0.4375rem;
}
[data-vibeui-block="nativeselect-004"] label{
font-size:0.875rem;font-weight:600;line-height:1.3;cursor:pointer;
}
/* Раскрытому списку appearance:none не нужен: стрелки у него нет, а
   собственная рамка и скругление задаются напрямую. */
[data-vibeui-block="nativeselect-004"] select{
box-sizing:border-box;width:100%;padding:0.25rem;
font:inherit;font-size:0.9375rem;line-height:1.4;
color:var(--vibeui-nativeselect-004-fg);
background:var(--vibeui-nativeselect-004-surface);
border:1px solid var(--vibeui-nativeselect-004-field-border);
border-radius:var(--vibeui-nativeselect-004-radius);
transition:border-color .16s ease,box-shadow .16s ease;
}
[data-vibeui-block="nativeselect-004"] select:focus-visible{
outline:none;border-color:var(--vibeui-nativeselect-004-accent);
box-shadow:0 0 0 3px color-mix(in oklab,var(--vibeui-nativeselect-004-accent) 22%,transparent);
}
[data-vibeui-block="nativeselect-004"] option{
padding:0.3125rem 0.4375rem;border-radius:0.375rem;
}
[data-vibeui-block="nativeselect-004"] option:checked{
background:color-mix(in oklab,var(--vibeui-nativeselect-004-accent) 16%,transparent);
color:var(--vibeui-nativeselect-004-fg);
}
[data-vibeui-block="nativeselect-004"] [data-part="hint"]{
margin:0;font-size:0.8125rem;line-height:1.5;
color:var(--vibeui-nativeselect-004-muted);
}
[data-vibeui-block="nativeselect-004"] kbd{
padding:0.0625rem 0.3125rem;border-radius:0.25rem;
background:var(--vibeui-nativeselect-004-key-bg);
border:1px solid var(--vibeui-nativeselect-004-field-border);
font-family:inherit;font-size:0.75rem;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="nativeselect-004"] *{animation:none!important;transition:none!important}}
`

/**
 * Множественный нативный select с раскрытым списком и подсказкой о
 * клавишах выбора. Один файл, ноль зависимостей.
 */
export function Nativeselect004({
  label = "Языки интерфейса",
  hint = "Несколько — с зажатой клавишей, подряд — с Shift.",
  options = [
    "Русский",
    "English",
    "Deutsch",
    "Français",
    "Español",
    "Português",
  ],
  rows = 5,
  accent,
  className,
  style,
  ...props
}: Nativeselect004Props) {
  const id = useId()
  const hintId = `${id}-hint`
  const palette = {
    ...(accent ? { "--vibeui-nativeselect-004-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-nativeselect-004" precedence="medium">
        {STYLES}
      </style>
      <div
        data-vibeui-block="nativeselect-004"
        className={className}
        style={palette}
      >
        <label htmlFor={id}>{label}</label>
        <select
          {...props}
          id={id}
          name="languages"
          multiple
          size={rows}
          defaultValue={["Русский"]}
          aria-describedby={hintId}
        >
          {options.map((option) => (
            <option key={option} value={option}>
              {option}
            </option>
          ))}
        </select>
        <p data-part="hint" id={hintId}>
          <kbd>Ctrl</kbd> / <kbd>⌘</kbd> {hint}
        </p>
      </div>
    </>
  )
}