Select
A dropdown for selecting one option from a list, with support for grouping and placeholders. Built on native `<select>` element.
---import Label from '@bejamas/ui/components/Label.astro';import Select from '@bejamas/ui/components/Select.astro';---
<div class="flex flex-col gap-2"> <Label for="fruits">Your favorite fruit</Label> <Select class="w-[180px]"> <Select part="indicator" /> <Select part="control" id="fruits" name="fruits"> <Select part="placeholder">Select a fruit</Select> <Select part="group" label="Fruits"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select> <Select part="group" label="Vegetables"> <Select part="option" value="carrot">Carrot</Select> <Select part="option" value="broccoli">Broccoli</Select> </Select> </Select> </Select></div>Installation
Section titled “Installation” bunx bejamas add select npx bejamas add select pnpm dlx bejamas add select yarn dlx bejamas add select---/** * @component Select * @title Select * @description A dropdown for selecting one option from a list, with support for grouping and placeholders. Built on native `<select>` element. * * @preview * * <div class="flex flex-col gap-2"> * <Label for="fruits">Your favorite fruit</Label> * <Select class="w-[180px]"> * <Select part="indicator" /> * <Select part="control" id="fruits" name="fruits"> * <Select part="placeholder">Select a fruit</Select> * <Select part="group" label="Fruits"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * <Select part="group" label="Vegetables"> * <Select part="option" value="carrot">Carrot</Select> * <Select part="option" value="broccoli">Broccoli</Select> * </Select> * </Select> * </Select> * </div> * * @usage * * ```astro * --- * import Select from '@/components/ui/Select.astro'; * --- * * <Select> * <Select part="indicator" /> * <Select part="control"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * </Select> * ``` * * ### Parts * * - root (default): wrapper container (relative) * - control: native `<select>` element * - option: native `<option>` element * - group: native `<optgroup>` element * - placeholder: hidden, selected `<option>` for placeholder text * - indicator: decorative dropdown icon (optional) * * @examples * * ### Default * * <Select> * <Select part="indicator" /> * <Select part="control"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * </Select> * * ### Sizes * * <div class="flex flex-col items-start gap-8 sm:flex-row"> * <Select class="w-[180px]"> * <Select part="control" size="sm"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * <Select part="indicator" /> * </Select> * <Select class="w-[180px]"> * <Select part="control" size="default"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * <Select part="indicator" /> * </Select> * <Select class="w-[180px]"> * <Select part="control" size="lg"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * <Select part="indicator" /> * </Select> * </div> * * ### Custom indicator * * <Select class="w-[180px]"> * <Select part="indicator"> * <ChevronsDownIcon class="size-4" /> * </Select> * <Select part="control" size="default" variant="default"> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * </Select> * * ### With placeholder * * Placeholder is a hidden, selected `option` for placeholder text, it must be a child of the `control` part. * * <Select class="w-[180px]"> * <Select part="indicator" /> * <Select part="control"> * <Select part="placeholder">Select a fruit</Select> * <Select part="option" value="apple">Apple</Select> * <Select part="option" value="banana">Banana</Select> * </Select> * </Select> * */
import { ChevronDown } from "@lucide/astro";import type { HTMLAttributes, HTMLTag } from "astro/types";import { cva } from "class-variance-authority";import { cn } from "@bejamas/ui/lib/utils";
type SelectPart = | "root" | "control" | "option" | "group" | "placeholder" | "indicator";
type Variant = "default" | "ghost";type Size = "default" | "sm" | "lg";
type RootProps = { part?: "root"; class?: string;} & HTMLAttributes<"div">;
type ControlProps = { part: "control"; variant?: Variant; size?: Size; showCheckmark?: boolean; class?: string;} & HTMLAttributes<"select">;
type OptionProps = { part: "option"; class?: string; value: string | number; selected?: boolean; disabled?: boolean;} & HTMLAttributes<"option">;
type GroupProps = { part: "group"; class?: string; label?: string; disabled?: boolean;} & HTMLAttributes<"optgroup">;
type PlaceholderProps = { /** Placeholder inside <select>, renders as a hidden, selected empty <option> */ part: "placeholder"; class?: string;} & HTMLAttributes<"option">;
type IndicatorProps = { part: "indicator"; class?: string;} & HTMLAttributes<"svg">;
type Props = | RootProps | ControlProps | OptionProps | GroupProps | PlaceholderProps | IndicatorProps;
const { part: rawPart, class: className = "", variant, size, showCheckmark = true, label, value, selected, disabled, ...rest} = Astro.props as Props;
const baseClasses = "block w-full inline-flex items-center select appearance-none relative shadow-xs rounded-md bg-background ps-4 pe-10 py-2 text-sm text-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring disabled:cursor-not-allowed disabled:opacity-50 [&_option]:rounded-md [&_option]:p-2";
const selectVariants = cva(baseClasses, { variants: { variant: { default: "border border-border", ghost: "border-0 bg-transparent shadow-none focus:bg-background", }, size: { default: "h-9 px-3 py-2 text-sm", sm: "h-8.5 text-xs rounded-lg gap-1.5 px-3.5 has-[>svg]:px-2.5", lg: "h-10 text-sm rounded-lg px-4 has-[>svg]:px-4 has-[.badge:last-child]:pr-3 [&_.badge:last-child]:ml-1.5", }, }, defaultVariants: { variant: "default", size: "default", },});
const part: SelectPart = (rawPart ?? "root") as SelectPart;---
{ part === "root" && (() => { return ( <div {...rest} data-slot="select" class={cn("relative", className)}> <slot name="control" /> <slot name="indicator" /> <slot /> </div> ); })()}
{ part === "control" && (() => { const classes = cn( "select", selectVariants({ variant: (variant as Variant) ?? "default", size: (size as Size) ?? "default", }), !showCheckmark && "hide-checkmark", className, ); return ( <select {...rest} slot="control" data-slot="select-control" class={classes} > <slot name="placeholder" /> <slot /> </select> ); })()}
{ part === "option" && (() => { return ( <option {...rest} value={value as any} selected={selected as any} disabled={disabled as any} data-slot="select-option" class={cn("text-foreground text-sm", className)} > <slot /> </option> ); })()}
{ part === "placeholder" && (() => { return ( <option {...rest} slot="placeholder" data-slot="select-placeholder" value="" disabled selected hidden class={cn("text-muted-foreground", className)} > <slot /> </option> ); })()}
{ part === "group" && (() => { return ( <optgroup {...rest} label={(label as any) ?? (rest as any).label} disabled={disabled as any} data-slot="select-group" class={cn( "text-muted-foreground mt-1 [&_+_optgroup]:mt-2 text-xs font-medium [&>option]:first:mt-1.5", className, )} > <slot /> </optgroup> ); })()}
{ part === "indicator" && (() => { return ( <span {...rest} slot="indicator" data-slot="select-indicator" class={cn( "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 z-10", className, )} > <slot> <ChevronDown class="size-4 text-foreground/60" /> </slot> </span> ); })()}
<style> @reference "tailwindcss"; @reference "../styles/globals.css";
/* Placeholder for checkmark hiding if supported by UA */ .hide-checkmark { option::checkmark { @apply hidden; } }
:where(.select) { &::picker(select) { @supports (appearance: base-select) { appearance: base-select; } border-color: var(--border); @apply rounded-lg my-2 p-1 shadow-lg bg-popover text-popover-foreground; } &::picker-icon { @apply hidden; }
:where(option) { @apply transition-colors duration-200 ease-in-out;
&:not(:disabled) { &:hover, &:focus-visible { @apply cursor-pointer outline-hidden; } &:active { box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--foreground); } } } }
@supports (appearance: base-select) { select { appearance: base-select; } }</style>---import Select from '@/components/ui/Select.astro';---
<Select> <Select part="indicator" /> <Select part="control"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select></Select>- root (default): wrapper container (relative)
- control: native
<select>element - option: native
<option>element - group: native
<optgroup>element - placeholder: hidden, selected
<option>for placeholder text - indicator: decorative dropdown icon (optional)
Examples
Section titled “Examples”Default
Section titled “Default”---import Select from '@bejamas/ui/components/Select.astro';---
<Select> <Select part="indicator" /> <Select part="control"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select></Select>---import Select from '@bejamas/ui/components/Select.astro';---
<div class="flex flex-col items-start gap-8 sm:flex-row"> <Select class="w-[180px]"> <Select part="control" size="sm"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select> <Select part="indicator" /> </Select> <Select class="w-[180px]"> <Select part="control" size="default"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select> <Select part="indicator" /> </Select> <Select class="w-[180px]"> <Select part="control" size="lg"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select> <Select part="indicator" /> </Select></div>Custom indicator
Section titled “Custom indicator”---import { ChevronsDownIcon } from '@lucide/astro';
import Select from '@bejamas/ui/components/Select.astro';---
<Select class="w-[180px]"> <Select part="indicator"> <ChevronsDownIcon class="size-4" /> </Select> <Select part="control" size="default" variant="default"> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select></Select>With placeholder
Section titled “With placeholder”Placeholder is a hidden, selected option for placeholder text, it must be a child of the control part.
---import Select from '@bejamas/ui/components/Select.astro';---
<Select class="w-[180px]"> <Select part="indicator" /> <Select part="control"> <Select part="placeholder">Select a fruit</Select> <Select part="option" value="apple">Apple</Select> <Select part="option" value="banana">Banana</Select> </Select></Select>