- Accordion
- Agent Thinking
- Alert
- Alert Dialog
- Announcement
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Checkbox
- Checkbox Card
- Code Block
- Collapsible
- Color Picker
- Combobox
- Command
- Composer
- Composer Loader
- Context Menu
- Data Grid
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- File Upload
- Hover Card
- Input
- Input Group
- Input Otp
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Meter
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Period Switcher
- Popover
- Progress
- Questionnaire
- Radio Card
- Radio Group
- Resizable
- Reveal
- Scroll Area
- Search Field
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Social Button
- Sonner
- Spinner
- Status Dot
- Switch
- Switch Card
- Table
- Tabs
- Tag Group
- Textarea
- Theme Toggle
- Time Field
- Toggle
- Toggle Group
- Tooltip
- Trace
- Tree View
- Agent Creator 01
- Agent Limits Card 01
- Agent Progress 01
- AI Chat 01
- AI Image Generation 01
- AI Profile 01
- Alerts Card 01
- Auth Card 01
- Calendar 01
- Composer 01
- Composer Attachments 01
- Composer Panel 01
- Finance Dashboard 01
- Home Dashboard 01
- Marketing Dashboard 01
- Medical Profile 01
- Meeting Scheduler 01
- Multi Agent Chat 01
- Notification Center 01
- Patient Info Card 01
- Project Board 01
- Questionnaire 01
- Settings Modal 01
- Sidebar 01
- Stat Cards 01
- Task List 01
- Web Search 01
- Activity Rings Card 01
- Area Chart Card 01
- Bar List Card 01
- Combo Chart Card 01
- Contributions Card 01
- Earnings Chart Card 01
- Funnel Chart Card 01
- Heatmap Chart Card 01
- Line Chart Card 01
- Most Active Days Card 01
- Orders Chart Card 01
- Radar Chart Card 01
- Radial Chart Card 01
- Revenue Chart Card 01
- Sankey Chart Card 01
- Scatter Chart Card 01
- Sleep Score Card 01
- Stage Bars Card 01
- Steps Card 01
Type to filter, then press Enter to pick.
Selected: Leo Park
Installation
$ pnpm dlx shadcn@latest add @cobalt/combobox
Source
"use client";
import { Combobox as ComboboxPrimitive } from "@base-ui/react";
import { RiCheckLine, RiArrowDownSLine, RiCloseLine } from "@remixicon/react";
import { cn } from "cn";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
const Combobox = ComboboxPrimitive.Root;
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;
}
/** Scroll distance, in px, over which the list's top fade ramps from 0 to 1. */
const SCROLL_FADE_DISTANCE = 24;
/**
* The soft top edge of the list: a 24px masked 1px backdrop blur, a 16px
* masked 4px blur closer to the edge, and a wash of the popup surface. Each
* layer fades in over the first 24px scrolled, so the resting list keeps a
* plain top edge. Opacity goes on each layer, never on the wrapper: a
* translucent parent becomes the backdrop root and samples nothing.
*/
function ComboboxScrollFade({ progress }: { progress: number }) {
return (
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 z-10 h-6"
>
<div
className="absolute inset-0 backdrop-blur-[1px] [mask-image:linear-gradient(to_bottom,black,transparent)]"
style={{ opacity: progress }}
/>
<div
className="absolute inset-x-0 top-0 h-4 backdrop-blur-[4px] [mask-image:linear-gradient(to_bottom,black,transparent)]"
style={{ opacity: progress }}
/>
<div
className="absolute inset-0 bg-linear-to-b from-[var(--surface-raised,var(--popover))] to-transparent"
style={{ opacity: progress }}
/>
</div>
);
}
function ComboboxTrigger({
className,
children,
...props
}: ComboboxPrimitive.Trigger.Props) {
return (
<ComboboxPrimitive.Trigger
aria-label="Show suggestions"
data-slot="combobox-trigger"
className={cn(
"group/combobox-trigger flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-[6px] p-1 text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:text-[color:var(--input-disabled-foreground,var(--muted-foreground))] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<RiArrowDownSLine className="pointer-events-none size-4 transition-transform duration-200 ease-[ease] group-data-[popup-open]/combobox-trigger:rotate-180 motion-reduce:transition-none" />
</ComboboxPrimitive.Trigger>
);
}
function ComboboxClear({
className,
children,
...props
}: ComboboxPrimitive.Clear.Props) {
return (
<ComboboxPrimitive.Clear
aria-label="Clear value"
data-slot="combobox-clear"
className={cn(
"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-[6px] p-1 text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:text-[color:var(--input-disabled-foreground,var(--muted-foreground))] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children ?? <RiCloseLine className="pointer-events-none size-4" />}
</ComboboxPrimitive.Clear>
);
}
function ComboboxInput({
className,
children,
disabled = false,
showTrigger = true,
showClear = false,
size = "default",
leadingIcon,
...props
}: Omit<ComboboxPrimitive.Input.Props, "size"> & {
showTrigger?: boolean;
showClear?: boolean;
/** A 20px icon at the start of the field, such as a search glass. */
leadingIcon?: React.ReactNode;
/** `sm` is the source's 32px field; `default` is its 36px field. */
size?: "default" | "sm";
}) {
const actionCount = Number(showTrigger) + Number(showClear);
const bothActions = showTrigger && showClear;
return (
<div
data-slot="combobox-input"
data-size={size}
className={cn(
"relative w-full",
// The clear button only mounts once there is a value, so the extra
// room for it is added by :has() rather than up front.
bothActions && "has-data-[slot=combobox-clear]:[&>input]:pr-[54px]",
className
)}
>
<ComboboxPrimitive.Input
disabled={disabled}
render={
<Input
data-size={size}
className={cn(
size === "sm" ? "h-8 pl-2.5" : "pl-3",
leadingIcon && "pl-[33px]",
actionCount > 0 && (size === "sm" ? "pr-7" : "pr-6")
)}
/>
}
{...props}
/>
{leadingIcon ? (
<span
aria-hidden
data-slot="combobox-leading-icon"
className="pointer-events-none absolute inset-y-0 start-[9px] flex items-center text-[color:var(--foreground-subtle,var(--muted-foreground))] [&_svg]:size-5 [&_svg]:shrink-0"
>
{leadingIcon}
</span>
) : null}
{actionCount > 0 && (
<div
className={cn(
"absolute inset-y-0 flex items-center gap-0.5",
size === "sm" ? "right-0.5" : "right-1"
)}
>
{showClear && <ComboboxClear disabled={disabled} />}
{showTrigger && <ComboboxTrigger disabled={disabled} />}
</div>
)}
{children}
</div>
);
}
function ComboboxContent({
className,
side = "bottom",
sideOffset = 4,
align = "start",
alignOffset = 0,
anchor,
children,
...props
}: ComboboxPrimitive.Popup.Props &
Pick<
ComboboxPrimitive.Positioner.Props,
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
>) {
return (
<ComboboxPrimitive.Portal>
<ComboboxPrimitive.Positioner
side={side}
sideOffset={sideOffset}
align={align}
alignOffset={alignOffset}
anchor={anchor}
className="isolate z-50"
>
<ComboboxPrimitive.Popup
data-slot="combobox-content"
data-chips={!!anchor}
className={cn(
"group/combobox-content relative flex max-h-(--available-height) w-(--anchor-width) max-w-[calc(100vw-32px)] origin-(--transform-origin) flex-col overflow-hidden rounded-[16px] border border-[var(--border-subtle,var(--border))] bg-[var(--surface-raised,var(--popover))] p-2 text-popover-foreground shadow-[shadow:var(--shadow-dropdown,0_1px_1px_0_rgb(0_0_0/0.04),0_4px_4px_0_rgb(0_0_0/0.02))] transition-[opacity,scale,filter] duration-150 ease-out data-[chips=true]:min-w-(--anchor-width) data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:blur-[2px] data-starting-style:scale-95 data-starting-style:opacity-0 data-starting-style:blur-[2px] motion-reduce:transition-none motion-reduce:data-ending-style:scale-100 motion-reduce:data-ending-style:opacity-100 motion-reduce:data-ending-style:blur-none motion-reduce:data-starting-style:scale-100 motion-reduce:data-starting-style:opacity-100 motion-reduce:data-starting-style:blur-none",
className
)}
{...props}
>
{children}
</ComboboxPrimitive.Popup>
</ComboboxPrimitive.Positioner>
</ComboboxPrimitive.Portal>
);
}
function ComboboxList({
className,
children,
onScroll,
...props
}: ComboboxPrimitive.List.Props) {
const [progress, setProgress] = React.useState(0);
return (
<div className="relative flex min-h-0 flex-col">
<ComboboxPrimitive.List
data-slot="combobox-list"
onScroll={(event) => {
setProgress(
Math.min(
1,
Math.max(0, event.currentTarget.scrollTop) / SCROLL_FADE_DISTANCE
)
);
onScroll?.(event);
}}
className={cn(
"flex max-h-[240px] min-h-0 w-full scroll-py-1 flex-col gap-1 overflow-y-auto overscroll-contain outline-none",
className
)}
{...props}
>
{children}
</ComboboxPrimitive.List>
<ComboboxScrollFade progress={progress} />
</div>
);
}
function ComboboxItem({
className,
children,
...props
}: ComboboxPrimitive.Item.Props) {
return (
<ComboboxPrimitive.Item
data-slot="combobox-item"
className={cn(
"flex w-full cursor-pointer items-center gap-2 rounded-[10px] p-2 text-start text-sm leading-5 font-medium text-foreground outline-none select-none transition-colors data-disabled:cursor-not-allowed data-disabled:text-[color:var(--foreground-disabled,var(--muted-foreground))] data-highlighted:bg-[var(--surface-hover,var(--accent))] motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
)}
{...props}
>
<span className="flex min-w-0 flex-1 items-center gap-2">{children}</span>
<ComboboxPrimitive.ItemIndicator
render={
<span className="flex size-4 shrink-0 items-center justify-center" />
}
>
<RiCheckLine className="pointer-events-none size-4 text-ring" />
</ComboboxPrimitive.ItemIndicator>
</ComboboxPrimitive.Item>
);
}
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
return (
<ComboboxPrimitive.Group
data-slot="combobox-group"
className={cn(
"flex flex-col gap-1 [&:not(:first-child)]:pt-2",
className
)}
{...props}
/>
);
}
function ComboboxLabel({
className,
...props
}: ComboboxPrimitive.GroupLabel.Props) {
return (
<ComboboxPrimitive.GroupLabel
data-slot="combobox-label"
className={cn(
"ps-2 pt-1 pb-0.5 text-sm leading-5 font-medium text-muted-foreground",
className
)}
{...props}
/>
);
}
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
return (
<ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
);
}
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
return (
<ComboboxPrimitive.Empty
data-slot="combobox-empty"
className={cn(
"hidden w-full items-center px-2 py-2 text-sm leading-5 font-medium text-[color:var(--combobox-empty-foreground,var(--muted-foreground))] group-data-empty/combobox-content:flex",
className
)}
{...props}
/>
);
}
function ComboboxSeparator({
className,
...props
}: ComboboxPrimitive.Separator.Props) {
return (
<ComboboxPrimitive.Separator
data-slot="combobox-separator"
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
);
}
function ComboboxChips({
className,
...props
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
ComboboxPrimitive.Chips.Props) {
return (
<ComboboxPrimitive.Chips
data-slot="combobox-chips"
className={cn(
// cobalt-allow-focus-within -- text input (chips hold the combobox input)
"flex min-h-9 flex-wrap items-center gap-1.5 rounded-[8px] border border-input bg-transparent bg-clip-padding px-2.5 py-1.5 text-sm shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1.5 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
className
)}
{...props}
/>
);
}
function ComboboxChip({
className,
children,
showRemove = true,
...props
}: ComboboxPrimitive.Chip.Props & {
showRemove?: boolean;
}) {
return (
<ComboboxPrimitive.Chip
data-slot="combobox-chip"
className={cn(
"flex h-[calc(--spacing(5.5))] w-fit items-center justify-center gap-1 rounded-[6px] bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
className
)}
{...props}
>
{children}
{showRemove && (
<ComboboxPrimitive.ChipRemove
render={<Button variant="ghost" size="icon-xs" />}
className="-ml-1 opacity-50 hover:opacity-100"
data-slot="combobox-chip-remove"
>
<RiCloseLine className="pointer-events-none" />
</ComboboxPrimitive.ChipRemove>
)}
</ComboboxPrimitive.Chip>
);
}
function ComboboxChipsInput({
className,
...props
}: ComboboxPrimitive.Input.Props) {
return (
<ComboboxPrimitive.Input
data-slot="combobox-chip-input"
className={cn("min-w-16 flex-1 outline-none", className)}
{...props}
/>
);
}
function useComboboxAnchor() {
return React.useRef<HTMLDivElement | null>(null);
}
export {
Combobox,
ComboboxInput,
ComboboxContent,
ComboboxList,
ComboboxItem,
ComboboxGroup,
ComboboxLabel,
ComboboxCollection,
ComboboxEmpty,
ComboboxSeparator,
ComboboxChips,
ComboboxChip,
ComboboxChipsInput,
ComboboxTrigger,
ComboboxValue,
useComboboxAnchor,
}; On this page