Glow Select
The select the field family was missing: GlowInput's trigger, StickerPopover's menu, Radix Select's behaviour.
InputsClient component@radix-ui/react-selectlucide-react
Preview
Sets the export size.
That family is not licensed for this workspace.
layer
Title block
Installation
The CLI writes the source into your project and pulls in whatever it depends on.
$
pnpm dlx shadcn@latest add @duck/glow-selectPulls in @duck/theme, @duck/glow-input, @duck/sticker-popover, @duck/hud-label. Already installed items are skipped.
Usage
import { GlowSelect, GlowSelectRoot, GlowSelectTrigger, GlowSelectValue, GlowSelectContent, GlowSelectItem, GlowSelectGroup, GlowSelectLabel, GlowSelectSeparator } from "@/components/ui/glow-select"The file lands in components/ui/glow-select.tsx and belongs to you from that point on. Edit it in place rather than wrapping it.
Props
Everything not listed here is forwarded to the underlying element.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Controlled selection. Pair with onValueChange. |
| defaultValue | string | - | Uncontrolled starting selection. |
| onValueChange | (value: string) => void | - | Fires with the chosen item's value. |
| placeholder | string | - | Shown until something is chosen, in the placeholder colour — the value is text the user did not type. |
| size | "sm" | "default" | "default" | sm is the 32px rail size, beside a slider row or an icon button. |
| frame | boolean | true | The sticker edge and the focus glow on the trigger. Off for a select that is a row action rather than a field. |
| chevron | boolean | true | Draw the chevron. Off for a trigger that is an icon and nothing else. |
| contentClassName | string | - | Sizing for the menu. It matches the trigger's width until told otherwise; className styles the trigger. |
Rules
What keeps this component from turning into noise.
- The trigger wears GlowInput's exported class strings and the menu wears StickerPopover's exported STICKER_SURFACE. Both are imports, not copies, so a select next to a field cannot drift from it — which is what every hand-rolled local select eventually does.
- This is the one place duck breaks its own rule 7. A stock shadcn select beside a GlowInput reads as a different design system: 1px against the 3px die-cut edge, --radius-md against rounded-lg, no lime glow, and a menu made of other material.
- Radix rather than a native <select>, unlike DuckSwitch and DuckSlider: the option list of a native select is drawn by the OS and cannot take the die-cut edge, which is half of what this component is for. The hidden native select underneath still submits in a plain form.
- The open trigger keeps the glow through data-[state=open], because a pointer that opened the menu got focus but not focus-visible — without it the frame goes cold while its own menu is up.
- Inside GlowField, pass nothing extra: the id, aria-describedby and aria-invalid it clones onto its child land on the trigger.
