Status select
A select whose options carry a monochrome icon in the trigger and in the list.
npx shadcn@latest add https://ui.goodtabs.co/r/status-select.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/status-select
The four issue states with In progress chosen. The icon travels into the trigger.
Small, inline
GT-142Focus ring clips on the save button
The sm size at the end of an issue row.
Custom options
A priority set with its own icons passed through options.
Usage
import { StatusSelect } from "@/components/ui/status-select"
<StatusSelect defaultValue="in-progress" onValueChange={setStatus} aria-label="Status" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| options | { value: string; label: string; icon: ComponentType<{ className?: string }> }[] | issueStatusOptions | The options, in list order. Defaults to Backlog, In progress, Done and Cancelled. |
| value, defaultValue, onValueChange | radix Select props | Controlled or uncontrolled, as in shadcn select. | |
| placeholder | string | "Status" | Text shown before a value is chosen. |
| size | "sm" | "default" | "default" | Trigger height: 28px or 32px. |
Source
status-select/status-select.tsx
"use client";
import * as React from "react";
import { CircleCheckIcon, CircleDotIcon, CircleIcon, CircleXIcon } from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { cn } from "@/lib/utils";
export type StatusOption = {
/** The key reported in onValueChange. */
value: string;
/** Item text, also shown in the trigger. */
label: string;
/** An icon component, for example a lucide icon. Rendered in the trigger and in the list. */
icon: React.ComponentType<{ className?: string }>;
};
/** The usual four issue states. Pass your own options to replace them. */
export const issueStatusOptions: StatusOption[] = [
{ value: "backlog", label: "Backlog", icon: CircleIcon },
{ value: "in-progress", label: "In progress", icon: CircleDotIcon },
{ value: "done", label: "Done", icon: CircleCheckIcon },
{ value: "cancelled", label: "Cancelled", icon: CircleXIcon },
];
type RootProps = React.ComponentProps<typeof Select>;
type StatusSelectProps = Omit<React.ComponentProps<typeof SelectTrigger>, "value" | "defaultValue" | "children"> &
Pick<RootProps, "value" | "defaultValue" | "onValueChange" | "name" | "disabled" | "required"> & {
/** The options, in list order. */
options?: StatusOption[];
/** Text shown before a value is chosen. */
placeholder?: string;
/** Trigger height: sm is 28px, default is 32px. */
size?: "sm" | "default";
};
/**
* A select whose options carry an icon, shown in the trigger and in the list.
* Icons are monochrome: muted at rest, foreground when the item is highlighted or chosen.
*/
export function StatusSelect({
options = issueStatusOptions,
placeholder = "Status",
size = "default",
value,
defaultValue,
onValueChange,
name,
disabled,
required,
className,
...props
}: StatusSelectProps) {
return (
<Select
value={value}
defaultValue={defaultValue}
onValueChange={onValueChange}
name={name}
disabled={disabled}
required={required}
>
<SelectTrigger size={size} className={cn("min-w-32", className)} {...props}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent position="popper" className="rounded-md">
{options.map(({ value: optionValue, label, icon: Icon }) => (
<SelectItem
key={optionValue}
value={optionValue}
className="h-8 rounded-sm focus:bg-muted focus:text-foreground"
>
<Icon className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
<span className="truncate">{label}</span>
</SelectItem>
))}
</SelectContent>
</Select>
);
}
For AI agents
Hand your agent this page, the markdown at /components/status-select/prompt.md, or the Copy AI prompt button above.
When to use
- To set the state of an issue, task, order or deploy from a short fixed list
- In table rows and detail headers where the icon makes the value scannable
- For any single choice where each option has a natural glyph, such as priority
When not to use
- For long lists or ones that need search, use a combobox
- For options without a meaningful icon, use the grouped select
- For picking several values at once, use a filter menu
Agent notes
- options defaults to the exported issueStatusOptions; pass your own array to replace it
- Each icon is a component, not an element: pass CircleIcon, not <CircleIcon />
- Icons stay monochrome by design; do not colour them per state
- Give the trigger an aria-label or a Label with htmlFor and an id, since the placeholder alone is not a name