Skip to content
weftGitHub

CompactSelect

A small Select with no floating label, for table rows, pagers and toolbars.

SourcePackage @weft-js/warpPlayground

Preview

tsx
import { CompactSelect } from '@weft-js/warp'export function Basic() {  return (    <CompactSelect label="Region" options={REGION_OPTIONS} defaultValue="eu-west" />  )}const REGIONS = [  { region: 'eu-west', requests: 34.2, share: 40 },  { region: 'us-east', requests: 24.8, share: 29 },  { region: 'ap-south', requests: 12.1, share: 14 },  { region: 'us-west', requests: 9.4, share: 11 },  { region: 'eu-north', requests: 5.6, share: 6 },]const REGION_OPTIONS = REGIONS.map(r => ({ value: r.region, label: r.region }))

Installation

npm install @weft-js/warp

Usage

tsx
import { CompactSelect } from '@weft-js/warp'<CompactSelect label="Region" options={REGION_OPTIONS} defaultValue="eu-west" />

States

A compact select can show a placeholder when empty, hold a chosen value, or be disabled.

tsx
import { CompactSelect } from '@weft-js/warp'export function States() {  return (    <>      <CompactSelect label="Region" placeholder="Region" options={REGION_OPTIONS} />      <CompactSelect label="Region" options={REGION_OPTIONS} defaultValue="us-east" />      <CompactSelect label="Region" options={REGION_OPTIONS} defaultValue="us-east" disabled />    </>  )}const REGIONS = [  { region: 'eu-west', requests: 34.2, share: 40 },  { region: 'us-east', requests: 24.8, share: 29 },  { region: 'ap-south', requests: 12.1, share: 14 },  { region: 'us-west', requests: 9.4, share: 11 },  { region: 'eu-north', requests: 5.6, share: 6 },]const REGION_OPTIONS = REGIONS.map(r => ({ value: r.region, label: r.region }))

Rows per page

A compact select suits a small choice beside other content, here the number of rows per page.

tsx
import { derived, state } from '@weft-js/core'import { CompactSelect, Row, Stack, Text } from '@weft-js/warp'export function Rows() {  const size = state<string | null>('5')  const rows = derived(() => PROJECTS.slice(0, Number(size.value)))  return (    <Stack gap="group">      <Stack gap="tight">        {rows.value.map(p => <Row key={p.id} justify="between"><Text>{p.name}</Text><Text subtle>{p.owner}</Text></Row>)}      </Stack>      <Row justify="between">        <Text subtle>{rows.value.length} of {PROJECTS.length}</Text>        <CompactSelect label="Rows per page" options={PAGE_SIZES} value={size} />      </Row>    </Stack>  )}type Status = 'Live' | 'Building' | 'Failed' | 'Paused'const PROJECTS: { id: string; name: string; owner: string; region: string; status: Status; deploys: number; uptime: number; latency: number; cost: number; updated: string }[] = [  { id: 'storefront', name: 'storefront', owner: 'Priya Raman', region: 'eu-west', status: 'Live', deploys: 412, uptime: 99.98, latency: 84, cost: 1240, updated: '2026-10-31' },  { id: 'checkout', name: 'checkout', owner: 'Tomás Oliveira', region: 'eu-west', status: 'Live', deploys: 288, uptime: 99.95, latency: 112, cost: 980, updated: '2026-10-31' },  { id: 'search-api', name: 'search-api', owner: 'Lena Fischer', region: 'us-east', status: 'Building', deploys: 196, uptime: 99.91, latency: 146, cost: 1610, updated: '2026-10-30' },  { id: 'billing', name: 'billing', owner: 'Mara Lindqvist', region: 'us-east', status: 'Live', deploys: 154, uptime: 99.99, latency: 73, cost: 720, updated: '2026-10-29' },  { id: 'mobile-bff', name: 'mobile-bff', owner: 'Sam Okafor', region: 'ap-south', status: 'Failed', deploys: 233, uptime: 99.42, latency: 208, cost: 860, updated: '2026-10-29' },  { id: 'notifier', name: 'notifier', owner: 'Jun Tanaka', region: 'eu-north', status: 'Live', deploys: 97, uptime: 99.97, latency: 58, cost: 310, updated: '2026-10-27' },  { id: 'reports', name: 'reports', owner: 'Noor Haddad', region: 'us-west', status: 'Paused', deploys: 41, uptime: 99.8, latency: 321, cost: 190, updated: '2026-10-22' },  { id: 'edge-cache', name: 'edge-cache', owner: 'Jun Tanaka', region: 'global', status: 'Live', deploys: 318, uptime: 100, latency: 21, cost: 1430, updated: '2026-10-31' },  { id: 'auth', name: 'auth', owner: 'Mara Lindqvist', region: 'eu-west', status: 'Live', deploys: 176, uptime: 99.99, latency: 66, cost: 540, updated: '2026-10-28' },  { id: 'ingest', name: 'ingest', owner: 'Lena Fischer', region: 'us-east', status: 'Live', deploys: 264, uptime: 99.93, latency: 131, cost: 2120, updated: '2026-10-30' },]const PAGE_SIZES = [{ value: '3', label: '3 rows' }, { value: '5', label: '5 rows' }, { value: '10', label: '10 rows' }]

A role in each row

Give each row its own compact select, with defaultValue set to the person's current role.

tsx
import { CompactSelect, Row, Stack, Text } from '@weft-js/warp'import type { Role } from '@weft-js/warp'export function Roles() {  return (    <Stack gap="related" divided>      {TEAM.map(p => (        <Row key={p.id} justify="between" gap="group">          <Stack gap="hair">            <Text>{p.name}</Text>            <Text role="caption" subtle>{p.email}</Text>          </Stack>          <CompactSelect label={`Role for ${p.name}`} options={TEAM_ROLES} defaultValue={p.role} />        </Row>      ))}    </Stack>  )}const PEOPLE = [  { id: 'mara', name: 'Mara Lindqvist', email: 'mara@weft.dev', role: 'Owner', team: 'Platform', initials: 'ML' },  { id: 'jun', name: 'Jun Tanaka', email: 'jun@weft.dev', role: 'Admin', team: 'Platform', initials: 'JT' },  { id: 'priya', name: 'Priya Raman', email: 'priya@weft.dev', role: 'Member', team: 'Web', initials: 'PR' },  { id: 'tomas', name: 'Tomás Oliveira', email: 'tomas@weft.dev', role: 'Member', team: 'Web', initials: 'TO' },  { id: 'lena', name: 'Lena Fischer', email: 'lena@weft.dev', role: 'Member', team: 'Data', initials: 'LF' },  { id: 'noor', name: 'Noor Haddad', email: 'noor@weft.dev', role: 'Viewer', team: 'Data', initials: 'NH' },  { id: 'sam', name: 'Sam Okafor', email: 'sam@weft.dev', role: 'Member', team: 'Mobile', initials: 'SO' },  { id: 'ines', name: 'Inés Vidal', email: 'ines@weft.dev', role: 'Viewer', team: 'Mobile', initials: 'IV' },]const ROLE_OPTIONS = [  { value: 'Admin', label: 'Admin', hint: 'Manages people and billing' },  { value: 'Member', label: 'Member', hint: 'Deploys and edits projects' },  { value: 'Viewer', label: 'Viewer', hint: 'Reads logs and metrics' },]const TEAM_ROLES = ROLE_OPTIONS.map(r => ({ value: r.value, label: r.value }))const TEAM = PEOPLE.slice(1, 5)

When to use

Use it for

  • A choice inside a row or a toolbar, repeated many times

Reach for something else

  • Select, when it's a field in a form
  • Menu, when it runs an action rather than holding a value

Playground

import { CompactSelect } from '@weft-js/warp'<CompactSelect options={REGIONS} label="Region" />

Accessibility

EnterSpace
Opens, or picks the highlighted option
↑↓
Moves through the options
Esc
Closes and returns focus
  • With nothing drawn for label, make it specific: "Role for Jun Tanaka", not "Role".

API Reference

PropTypeDefaultDescription
labelstring—The accessible name. Not drawn.
options(Option | OptionGroup)[]—The list, the same as Select.
placeholderstring—Sample text shown while empty. Never a substitute for a label.
valuestring | State<string | null> | null—A string, or a State to bind.
defaultValuestring | null—The starting value, when the component owns the state.
openboolean | State<boolean>—A state box that holds whether it is open.
defaultOpenbooleanfalseStarts open, when the component keeps its own open state.
presentationPresentation—auto opens a popover, or a sheet when the viewport is under 600px wide; popover and sheet force one.
searchableboolean—Puts a search field at the top of the list. On by itself past 15 options.
namestring—Puts it in a form with a hidden input.
idstring—The element id. One is made for you if you leave it out.
disabledbooleanfalseTurns it off, and takes it out of the tab order.
describedbystring—The id of text that explains it.
EventTypeDefaultDescription
onValueChange((value: string, reason: ChangeReason) => void)—Called with the picked option's value, and whether a pointer, a key or code picked it.
onOpenChange((open: boolean) => void)—Called with true when the list opens and false when it closes.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="field"] [data-part="control"]

controlsweep-basesweepinputvalue

RelatedSelectTable