htsx

Select

Select component for Hono JSX

Use Checkbox for multiple select. See Form for best practice.

Examples

Select a country.

// Simple
<Select id="theme" name="theme" label="Theme">
  <option value="system" selected>
    System
  </option>
  <option value="light">Light</option>
  <option value="dark">Dark</option>
</Select>
 
// Required / Error Message
<Select
  id="country"
  name="country"
  label="Country"
  invalid
  error="Select a country."
  required
>
  <option value="" selected>
    Select a country
  </option>
  <option value="jp">Japan</option>
  <option value="us">United States</option>
</Select>
 
// Grouped
<Select id="timezone" name="timezone" label="Timezone">
  <option value="" selected>
    Select a timezone
  </option>
  <optgroup label="Asia">
    <option value="asia-tokyo">Tokyo</option>
    <option value="asia-seoul">Seoul</option>
  </optgroup>
  <optgroup label="America">
    <option value="america-new-york">New York</option>
    <option value="america-los-angeles">Los Angeles</option>
  </optgroup>
</Select>
 
// Disabled
<Select id="disabled" label="Disabled" disabled>
  <option>Disabled</option>
</Select>
 

Code

ui/select.tsx
import type { Child, JSX } from "hono/jsx";
import { c } from "./c";
 
export function Select({
  id,
  label,
  invalid,
  error,
  required,
  class: custom,
  children,
  ...props
}: Omit<JSX.IntrinsicElements["select"], "multiple" | "size"> & {
  label?: string;
  invalid?: boolean;
  error?: string;
  children?: Child;
}) {
  const isInvalid = invalid || Boolean(error);
  const errorId = id && error ? `${id}-error` : undefined;
 
  return (
    <div class="flex w-full flex-col gap-1">
      {label ? (
        <label for={id} class="text-sm font-medium">
          {label}
          {required ? <span class="ml-1 text-destructive">*</span> : null}
        </label>
      ) : null}
      <select
        id={id}
        required={required}
        aria-invalid={isInvalid || undefined}
        aria-describedby={errorId}
        class={c(
          "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm transition disabled:cursor-not-allowed disabled:opacity-50 dark:bg-muted",
          {
            "border-destructive": isInvalid,
          },
          custom,
        )}
        {...props}
      >
        {children}
      </select>
      {error ? (
        <p id={errorId} class="text-sm text-destructive">
          {error}
        </p>
      ) : null}
    </div>
  );
}