Skip to content
goodtabs UI
BrowseBrowse components

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

PropTypeDefaultDescription
options{ value: string; label: string; icon: ComponentType<{ className?: string }> }[]issueStatusOptionsThe options, in list order. Defaults to Backlog, In progress, Done and Cancelled.
value, defaultValue, onValueChangeradix Select propsControlled or uncontrolled, as in shadcn select.
placeholderstring"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