stream/ui
Browse the library

Comic Select

A native select in the comic frame, with the arrow on a starburst that spins on focus.

betaComicSelect.tsx

Examples

Basic

comic-select/basic.tsx
import { ComicSelect } from "@streamuilib/react";

export default function ComicSelectBasic() {
  return (
    <label className="flex w-full max-w-xs flex-col gap-2">
      <span className="font-bold">Pick a power</span>
      <ComicSelect defaultValue="">
        <option value="" disabled>
          Choose one…
        </option>
        <option value="flight">Flight</option>
        <option value="strength">Super strength</option>
        <option value="speed">Speed</option>
        <option value="invisibility">Invisibility</option>
      </ComicSelect>
    </label>
  );
}

Invalid and disabled

comic-select/states.tsx
import { ComicSelect } from "@streamuilib/react";

export default function ComicSelectStates() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-6">
      <label className="flex flex-col gap-2">
        <span className="font-bold">Plan</span>
        <ComicSelect invalid defaultValue="">
          <option value="">Choose a plan…</option>
          <option value="free">Free</option>
          <option value="pro">Pro</option>
        </ComicSelect>
      </label>
      <label className="flex flex-col gap-2">
        <span className="font-bold">Region</span>
        <ComicSelect disabled defaultValue="eu">
          <option value="eu">Europe</option>
        </ComicSelect>
      </label>
    </div>
  );
}

Installation

bun add @streamuilib/react
Open in v0 (opens in a new tab)
usage.tsx
import { ComicSelect } from "@streamuilib/react";

<ComicSelect aria-label="Power">
  <option>Flight</option>
  <option>Speed</option>
</ComicSelect>

API

PropTypeDefaultDescription
invalidbooleanfalseSets aria-invalid and tints the frame pink.
frameClassNamestring—Classes for the outer frame.
...propsComponentProps<"select">—Every native select attribute; pass <option>s as children.

Accessibility

  • A native <select>: the platform's own picker on phones, typeahead, and full keyboard and screen reader support.
  • The starburst arrow is decorative and hidden.

Source

packages/react/src/ComicSelect.tsx
import { ChevronDown } from "lucide-react";
import { cn } from "./cn";
import { comicFrame, halftone } from "./comic";
import { SHAPES } from "./ComicBadge";
import type { SelectProps } from "./Select";

export type ComicSelectProps = SelectProps;

/**
 * A comic-book select: a native <select> (the platform's own picker,
 * typeahead, full keyboard and screen reader support) in the comic frame,
 * with the arrow on a little starburst that spins when the field has focus.
 */
export function ComicSelect({ invalid, frameClassName, className, children, ...props }: ComicSelectProps) {
  return (
    <div className={comicFrame({ invalid, className: cn("items-center", frameClassName) })} style={halftone(10)}>
      <select
        aria-invalid={invalid || undefined}
        className={cn(
          "h-11 w-full min-w-0 cursor-pointer appearance-none bg-transparent pr-12 pl-3 font-semibold focus:outline-none disabled:cursor-not-allowed",
          className,
        )}
        {...props}
      >
        {children}
      </select>
      <span aria-hidden className="pointer-events-none absolute right-1.5 grid size-9 place-items-center">
        <svg viewBox="0 0 100 100" overflow="visible" className="sui-comic-select-burst absolute inset-0 size-full">
          <path d={SHAPES.burst.d} className="fill-lime stroke-ink" strokeWidth={7} strokeLinejoin="round" />
        </svg>
        <ChevronDown className="relative size-4 text-on-bright" strokeWidth={3} />
      </span>
    </div>
  );
}