Select
A native select in the brutal frame, so phones get their own picker and keyboards work as expected.
stableSelect.tsx
Examples
Basic
select/basic.tsx
import { Select } from "@streamuilib/react";
export default function SelectBasic() {
return (
<div className="w-full max-w-xs">
<label htmlFor="select-region" className="mb-2 block font-bold">
Region
</label>
<Select id="select-region" defaultValue="bom">
<option value="iad">US East (Virginia)</option>
<option value="fra">Europe (Frankfurt)</option>
<option value="bom">Asia (Mumbai)</option>
<option value="syd">Oceania (Sydney)</option>
</Select>
</div>
);
}Invalid and disabled
Pick a role to send the invite.
select/states.tsx
import { Select } from "@streamuilib/react";
export default function SelectStates() {
return (
<div className="flex w-full max-w-xs flex-col gap-6">
<div>
<label htmlFor="select-role" className="mb-2 block font-bold">
Role
</label>
<Select id="select-role" defaultValue="" invalid aria-describedby="select-role-error">
<option value="" disabled>
Choose a role…
</option>
<option value="admin">Admin</option>
<option value="member">Member</option>
</Select>
<p id="select-role-error" className="mt-2 text-sm font-semibold">
Pick a role to send the invite.
</p>
</div>
<div>
<label htmlFor="select-plan" className="mb-2 block font-bold">
Plan
</label>
<Select id="select-plan" disabled>
<option>Enterprise (managed)</option>
</Select>
</div>
</div>
);
}Installation
bun add @streamuilib/reactusage.tsx
import { Select } from "@streamuilib/react";
<label htmlFor="region">Region</label>
<Select id="region">
<option value="bom">Asia (Mumbai)</option>
</Select>API
| Prop | Type | Default | Description |
|---|---|---|---|
| invalid | boolean | false | Sets aria-invalid and tints the field pink. |
| frameClassName | string | — | Classes for the outer frame. |
| className | string | — | Classes for the <select> itself. |
| children | <option> elements | — | Options and optgroups, exactly as in native HTML. |
| ...props | ComponentProps<"select"> | — | Every native select attribute. |
Accessibility
- It's the platform's own <select>: typeahead, arrow keys, and the native picker on touch devices all work.
- Use a disabled empty first option as a placeholder, and mark the field invalid if the form is submitted without a choice.
Source
packages/react/src/Select.tsx
import type { ComponentProps } from "react";
import { ChevronDown } from "lucide-react";
import { cn } from "./cn";
import { fieldFrame } from "./field";
export type SelectProps = ComponentProps<"select"> & {
/** Marks the field invalid (sets `aria-invalid`) and tints it. */
invalid?: boolean;
/** Classes for the outer frame. `className` goes to the `<select>`. */
frameClassName?: string;
};
/**
* A native <select> in the brutal frame. Native means the platform's own
* picker on phones, typeahead, and full keyboard and screen reader support.
*/
export function Select({ invalid, frameClassName, className, children, ...props }: SelectProps) {
return (
<div className={fieldFrame({ invalid, className: cn("relative items-center", frameClassName) })}>
<select
aria-invalid={invalid || undefined}
className={cn(
"h-11 w-full min-w-0 cursor-pointer appearance-none bg-transparent pr-10 pl-3 focus:outline-none disabled:cursor-not-allowed",
className,
)}
{...props}
>
{children}
</select>
<ChevronDown aria-hidden className="pointer-events-none absolute right-3 size-4.5 text-ink" strokeWidth={2.5} />
</div>
);
}