Select
Single-choice picker with a token-matched trigger and floating list.
Composition
The preferred shape for composing the installed primitive from its exported parts.
Parts
Exported compound parts from the installed source.
Select ├── SelectTrigger ├── SelectValue ├── SelectContent └── SelectItem
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/select.tsx. Install it on its own with the command above, or inspect the source below.
npx shadcn@latest add https://control-ui.dev/r/select.jsonKnobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-button-* · 17 knobsHow the cascade resolves--cui-button-radius<length-percentage>var(--radius-control)--cui-button-gap<length-percentage>0.375rem--cui-button-icon<length>1rem--cui-button-bg<color>transparent--cui-button-bg-image*none--cui-button-foreground<color>var(--muted-foreground)--cui-button-hover-bg<color>oklch(from var(--foreground) l c h / 0.06)--cui-button-hover-foreground<color>var(--foreground)--cui-button-press-bg<color>var(--cui-button-hover-bg)--cui-button-press-scale<number>0.98--cui-button-active-bg<color>oklch(from var(--foreground) l c h / 0.08)--cui-button-active-foreground<color>var(--foreground)--cui-button-active-hover-bg<color>var(--cui-button-active-bg)--cui-button-shadow*0 0 transparent--cui-button-hover-shadow*var(--cui-button-shadow)--cui-button-press-shadow*var(--cui-button-hover-shadow)--cui-button-active-shadow*var(--cui-button-shadow)--cui-popup-* · 16 knobsHow the cascade resolves--cui-popup-radius*var(--radius-popover)--cui-popup-background<color>var(--popover)--cui-popup-foreground<color>var(--popover-foreground)--cui-popup-border-color<color>var(--border)--cui-popup-border-width<length>1px--cui-popup-shadow*var(--shadow-pop)--cui-popup-backdrop-filter*blur(var(--backdrop-blur-popover))--cui-popup-backdrop-background<color>oklch(from var(--foreground) l c h / var(--overlay-opacity))--cui-popup-item-radius<length-percentage>var(--radius-popup-item)--cui-popup-item-foreground<color>var(--foreground)--cui-popup-item-highlight-background<color>oklch(from var(--foreground) l c h / 0.06)--cui-popup-item-highlight-foreground<color>var(--cui-popup-item-foreground)--cui-popup-item-highlight-muted-foreground<color>var(--muted-foreground)--cui-popup-item-disabled-opacity<number>0.4--cui-popup-separator-color<color>var(--border)--cui-popup-shortcut-foreground<color>var(--muted-foreground)Installed dependencies
Support files installed with this primitive — shared ones arrive once with your first Control UI component. Public dependencies stay linked to their own pages.
src/registry/sources/control-ui/surface-variants.tsSupportsrc/registry/sources/control-ui/control-variants.tsSupportRaw code
This primitive's source and the support files it installs with
"use client";import { Select as SelectPrimitive } from "@base-ui/react/select";import type { ComponentProps, CSSProperties, ReactNode } from "react";import type { ControlledChoice } from "@/components/control-ui/control-props";import type { ControlSize } from "@/components/control-ui/control-variants";import { buttonGapClass, controlSize } from "@/components/control-ui/control-variants";import type { ButtonKnobStyle } from "@/components/control-ui/knob-contracts/button-knobs";import type { PopupKnobStyle } from "@/components/control-ui/knob-contracts/popup-knobs";import { cn } from "@/components/control-ui/lib/cn";import { skinEffects, skinId } from "@/components/control-ui/skin";import { popupItemStructureClasses } from "@/components/control-ui/surface-variants";export type SelectProps<TValue extends string = string> = ControlledChoice<TValue> & { disabled?: boolean; name?: string; children?: ReactNode;};export const selectTriggerVariants = ["surface", "ghost"] as const;export type SelectTriggerVariant = (typeof selectTriggerVariants)[number];export type SelectTriggerProps = Omit<ComponentProps<"button">, "style"> & { style?: CSSProperties & ButtonKnobStyle } & { size?: ControlSize; variant?: SelectTriggerVariant;};export type SelectValueProps = { placeholder?: ReactNode; children?: ReactNode | ((value: string) => ReactNode);};export type SelectContentProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle };export type SelectItemProps = Omit<ComponentProps<"div">, "style"> & { style?: CSSProperties & PopupKnobStyle } & { value: string; label?: string; disabled?: boolean;};type RefinedSelectTriggerProps = SelectTriggerProps & Pick<ComponentProps<typeof SelectPrimitive.Trigger>, "nativeButton" | "render"> & { style?: CSSProperties & PopupKnobStyle };// Base UI's own root is generic (`SelectRoot<Value>`) and reports null once selection is cleared;// naming TValue here is what keeps caller's literal union alive, and null never reaches declared value.export function Select<TValue extends string = string>({ children, onValueChange, ...props }: SelectProps<TValue>) { return ( <SelectPrimitive.Root<TValue> {...props} onValueChange={(value) => { if (value !== null) onValueChange?.(value); }} > {children} </SelectPrimitive.Root> );}export function SelectTrigger({ size = "sm", variant = "surface", className, children, disabled, ...props }: RefinedSelectTriggerProps) { return ( <SelectPrimitive.Trigger data-control-ui="select" data-control-family="button" data-popup-kind="select" data-slot="trigger" data-control="true" data-size={size} data-variant={variant} data-tone="neutral" data-shape="default" className={cn( "group relative isolate inline-flex shrink-0 items-center justify-between overflow-visible", controlSize({ size }), buttonGapClass, className, )} disabled={disabled} {...props} > <span data-control-ui="button" data-control-family="button" data-slot="content" className="relative z-[1] inline-flex min-w-0 items-center justify-center gap-[inherit]" > {children} </span> <SelectPrimitive.Icon data-control-ui="select" data-control-family="popup" data-popup-kind="select" data-slot="icon" className="relative z-[1]" > <svg viewBox="0 0 12 12" className="size-3" aria-hidden="true" fill="none"> <path d="M3 4.5 6 7.5 9 4.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" /> </svg> </SelectPrimitive.Icon> </SelectPrimitive.Trigger> );}export function SelectValue({ children, ...props }: SelectValueProps) { return ( <SelectPrimitive.Value className="min-w-0 truncate" {...props}> {children} </SelectPrimitive.Value> );}export function SelectContent({ className, children, ...props }: SelectContentProps) { return ( <SelectPrimitive.Portal> {/* portal lands outside container-scoped skin root, so scope is re-asserted here */} <SelectPrimitive.Positioner data-skin={skinId()} data-effects={skinEffects()} side="bottom" align="start" sideOffset={6} className="z-[80]" > <SelectPrimitive.Popup data-control-ui="select" data-popup-kind="select" data-slot="content" data-surface="floating" data-control-family="popup" data-popup-part="list-surface" className={cn("max-h-[min(20rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto", className)} {...props} > {children} </SelectPrimitive.Popup> </SelectPrimitive.Positioner> </SelectPrimitive.Portal> );}export function SelectItem({ className, children, disabled, ...props }: SelectItemProps) { return ( <SelectPrimitive.Item data-control-ui="select" data-popup-kind="select" data-slot="item" data-control-family="popup" data-popup-part="item" disabled={disabled} className={cn(popupItemStructureClasses, "px-[calc(var(--padding-x)*0.5)] py-1", className)} {...props} > <SelectPrimitive.ItemText className="flex min-w-0 flex-1 items-center gap-2">{children}</SelectPrimitive.ItemText> <span data-control-ui="select" data-control-family="popup" data-popup-kind="select" data-slot="item-indicator" className="flex size-3.5 shrink-0 items-center justify-center" > <SelectPrimitive.ItemIndicator> <svg viewBox="0 0 12 12" className="size-3" aria-hidden="true" fill="none"> <path d="M2.5 6.5 5 9l4.5-5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" /> </svg> </SelectPrimitive.ItemIndicator> </span> </SelectPrimitive.Item> );}