Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi select ? #66

Open
Yolo390 opened this issue Feb 8, 2023 · 91 comments
Open

Multi select ? #66

Yolo390 opened this issue Feb 8, 2023 · 91 comments

Comments

@Yolo390
Copy link

Yolo390 commented Feb 8, 2023

Hi there βœ‹πŸΌ

thanks for this amazing components !

Is there a way to select multiple data with the Select component ?

Thanks !

@shadcn
Copy link
Collaborator

shadcn commented Feb 8, 2023

Unfortunately no. The <Select /> element does not support multi-select. There's an issue on the Radix UI repo I'm following too: radix-ui/primitives#1342

@jackblackCH
Copy link

πŸ‘ Would love to have a Multi Select

@franciscohanna92
Copy link

@its-monotype Listbox from HeadlessUI (same creators as TailwindCSS) has support for multi-select

@99sdawkhar
Copy link

Hey @Flo-Slv, I also wanted a multi-select, one of my colleague suggested to use a dropdown-menu with checkboxes. It worked well for my use-case. Checked it out here.

@Yolo390
Copy link
Author

Yolo390 commented Feb 20, 2023

Hey @Flo-Slv, I also wanted a multi-select, one of my colleague suggested to use a dropdown-menu with checkboxes. It worked well for my use-case. Checked it out here.

Hey ! It can be a workaround until they implement a native solution ! Thanks !

@hiql
Copy link

hiql commented Mar 24, 2023

https://github.com/colepeters/multiselect

What about this?

@evangow
Copy link

evangow commented Jun 2, 2023

I'd love see a multi-select with labels (instead of saying something like "4 items selected"), which is incredibly valuable for adding "tags" to things - a fairly common use case

Here's the one I'm currently using (not based on tailwind-css)
https://react.semantic-ui.com/modules/dropdown/

Here's a version using tailwind: https://demo-react-tailwindcss-select.vercel.app/
Github repo here: https://github.com/onesine/react-tailwindcss-select
^The downside to this one is that it doesn't look like it handles aria support / keyboard navigation

@oliviertassinari
Copy link

oliviertassinari commented Jun 6, 2023

There is a headless multi-select combobox in Base UI (import X from @mui/base/useAutocomplete) which is supposed to be feature-rich:

Screenshot 2023-06-06 at 14 19 53

https://mui.com/material-ui/react-autocomplete/#customized-hook

and small:

Screenshot 2023-06-06 at 14 17 34

https://bundlephobia.com/package/@mui/base@5.0.0-beta.4

maybe to consider as a base to style on top of, it could be a temporary solution for radix-ui/primitives#1342.

@evangow
Copy link

evangow commented Jun 12, 2023

In case anyone else comes to this issue looking for a solution, @mxkaske just dropped a mutli-select component built with cmdk and shadcn components.

Demo here: https://craft.mxkaske.dev/post/fancy-multi-select

Source here: https://github.com/mxkaske/mxkaske.dev/blob/main/components/craft/fancy-multi-select.tsx

@zachrip
Copy link

zachrip commented Jun 14, 2023

In case anyone else comes to this issue looking for a solution, @mxkaske just dropped a mutli-select component built with cmdk and shadcn components.

Demo here: https://craft.mxkaske.dev/post/fancy-multi-select

Source here: https://github.com/mxkaske/mxkaske.dev/blob/main/components/craft/fancy-multi-select.tsx

This isn't accessible

@evangow
Copy link

evangow commented Jun 14, 2023

@zachrip you can drop an issue in the repo here: https://github.com/mxkaske/mxkaske.dev/issues

@console-logs
Copy link

I tweaked Headless UI Listbox component to achieve the desired UI.

Here is the example code:

import { Listbox, Transition } from '@headlessui/react';
import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons';
import React from 'react';

export default function MultiSelect() {
	const [selected, setSelected] = React.useState(['None']);
	const [options, setOptions] = React.useState<string[]>([]);

	React.useEffect(() => {
		setOptions(['None', 'Apple', 'Orange', 'Banana', 'Grapes']);
	}, []);

	return (
		<Listbox
			value={selected}
			onChange={setSelected}
			multiple>
			<div className='relative'>
				<Listbox.Button className='flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50'>
					<span className='block truncate'> {selected.map(option => option).join(', ')}</span>
					<CaretSortIcon className='h-4 w-4 opacity-50' />
				</Listbox.Button>
				<Transition
					as={React.Fragment}
					leave='transition ease-in duration-100'
					leaveFrom='opacity-100'
					leaveTo='opacity-0'>
					<Listbox.Options className='absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-popover py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm'>
						{options.map((option, optionIdx) => (
							<Listbox.Option
								key={optionIdx}
								className='relative cursor-default select-none py-1.5 pl-10 pr-4 text-sm rounded-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50'
								value={option}>
								{({ selected }) => (
									<>
										{option}
										{selected ? (
											<span className='absolute inset-y-0 right-2 flex items-center pl-3'>
												<CheckIcon className='h-4 w-4' />
											</span>
										) : null}
									</>
								)}
							</Listbox.Option>
						))}
					</Listbox.Options>
				</Transition>
			</div>
		</Listbox>
	);
}

Hope this works for you.

Cheers!

@dinogit
Copy link

dinogit commented Sep 13, 2023

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

@DarkAbhi
Copy link

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

You can create a PR for this to be supported officially?

DarkAbhi added a commit to DarkAbhi/terrum-admin-panel that referenced this issue Sep 20, 2023
@zmzlois
Copy link

zmzlois commented Sep 21, 2023

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

thank you, this is great. also wondering did anyone successfully make a form collect inputs correctly?

@Syammed2429
Copy link

Unfortunately no. The <Select /> element does not support multi-select. There's an issue on the Radix UI repo I'm following too: radix-ui/primitives#1342

Is there any component that has multi-select except the dropdown? @shadcn

@enesien
Copy link

enesien commented Oct 13, 2023

I created a multi input/select component but for tags that the user inputs rather than using a pre-defined list of options https://gist.github.com/enesien/03ba5340f628c6c812b306da5fedd1a4

@johnLamberts
Copy link

Hi all,

I have created component, I hope somebody will find it helpful:

import * as React from 'react'
import { cn } from "@/lib/utils"

import { Check, X, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
} from "@/components/ui/command"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/ui/popover"
import { Badge } from "@/components/ui/badge";


export type OptionType = {
    label: string;
    value: string;
}

interface MultiSelectProps {
    options: OptionType[];
    selected: string[];
    onChange: React.Dispatch<React.SetStateAction<string[]>>;
    className?: string;
}

function MultiSelect({ options, selected, onChange, className, ...props }: MultiSelectProps) {

    const [open, setOpen] = React.useState(false)

    const handleUnselect = (item: string) => {
        onChange(selected.filter((i) => i !== item))
    }

    return (
        <Popover open={open} onOpenChange={setOpen} {...props}>
            <PopoverTrigger asChild>
                <Button
                    variant="outline"
                    role="combobox"
                    aria-expanded={open}
                    className={`w-full justify-between ${selected.length > 1 ? "h-full" : "h-10"}`}
                    onClick={() => setOpen(!open)}
                >
                    <div className="flex gap-1 flex-wrap">
                        {selected.map((item) => (
                            <Badge
                                variant="secondary"
                                key={item}
                                className="mr-1 mb-1"
                                onClick={() => handleUnselect(item)}
                            >
                                {item}
                                <button
                                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                                    onKeyDown={(e) => {
                                        if (e.key === "Enter") {
                                            handleUnselect(item);
                                        }
                                    }}
                                    onMouseDown={(e) => {
                                        e.preventDefault();
                                        e.stopPropagation();
                                    }}
                                    onClick={() => handleUnselect(item)}
                                >
                                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                                </button>
                            </Badge>
                        ))}
                    </div>
                    <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-full p-0">
                <Command className={className}>
                    <CommandInput placeholder="Search ..." />
                    <CommandEmpty>No item found.</CommandEmpty>
                    <CommandGroup className='max-h-64 overflow-auto'>
                        {options.map((option) => (
                            <CommandItem
                                key={option.value}
                                onSelect={() => {
                                    onChange(
                                        selected.includes(option.value)
                                            ? selected.filter((item) => item !== option.value)
                                            : [...selected, option.value]
                                    )
                                    setOpen(true)
                                }}
                            >
                                <Check
                                    className={cn(
                                        "mr-2 h-4 w-4",
                                        selected.includes(option.value) ?
                                            "opacity-100" : "opacity-0"
                                    )}
                                />
                                {option.label}
                            </CommandItem>
                        ))}
                    </CommandGroup>
                </Command>
            </PopoverContent>
        </Popover>
    )
}

export { MultiSelect }

Use it like standalone component :

import * as React from 'react'
import { MultiSelect } from from "@/components/ui/multi-select"

function Demo() {
  const [selected, setSelected] = useState<string[]>([]);

  return (
    <MultiSelect
        options={[
          {
            value: "next.js",
            label: "Next.js",
          },
          {
            value: "sveltekit",
            label: "SvelteKit",
          },
          {
            value: "nuxt.js",
            label: "Nuxt.js",
          },
          {
            value: "remix",
            label: "Remix",
          },
          {
            value: "astro",
            label: "Astro",
          },
          {
            value: "wordpress",
            label: "WordPress",
          },
          {
            value: "express.js",
            label: "Express.js",
          },
        ]}
        selected={selected}
        onChange={setSelected}
        className="w-[560px]"
      />
  )
}

or part of React Hook Form:

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

I am getting an error through the selected item, I am using react hook form

<FormField control={form.control} name="authors" render={({ field: { ...field } }) => ( <FormItem className="mb-5"> <FormLabel>Author</FormLabel> <MultiSelect selected={field.value} options={authorsData} {...field} /> </FormItem> )} />
it says that, selected is not iterable, I already check the onSelect method from CommandItem but I can't find any solution

@dinogit
Copy link

dinogit commented Oct 23, 2023

@johnLamberts PR is still in progress, so until this is done, copy code from here, fix some imports and let me know does it work.

@johnLamberts
Copy link

johnLamberts commented Oct 23, 2023

@johnLamberts PR is still in progress, so until this is done, copy code from here, fix some imports and let me know does it work.

I am still getting the same error, I have already check the code.

import * as React from "react";
import { Badge } from "@/shared/components/ui/badge";
import { Button } from "@/shared/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
} from "@/shared/components/ui/command";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/shared/components/ui/popover";
import { cn } from "@/shared/lib/utils";
import { Check, ChevronsUpDown, X } from "lucide-react";
import { useState } from "react";

// export type OptionType = {
//   id: string;
//   value: string;
// };

export type OptionType = Record<"id" | "value", string>;

interface MultiSelectProps {
  options: Record<"id" | "value", string>[];
  selected: Record<"id" | "value", string>[];
  onChange: React.Dispatch<
    React.SetStateAction<Record<"id" | "value", string>[]>
  >;
  className?: string;
}

const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>(
  ({ options, selected, onChange, className, ...props }, ref) => {
    const [open, setOpen] = useState(false);

    const handleUnselect = (item: Record<"id" | "value", string>) => {
      onChange(selected.filter((i) => i.id !== item.id));
    };

    return (
      <Popover open={open} onOpenChange={setOpen} {...props}>
        <PopoverTrigger asChild>
          <Button
            ref={ref}
            role="combobox"
            variant="outline"
            aria-expanded={open}
            className={`w-full justify-between  ${
              selected?.length > 1 ? "h-full" : "h-10"
            }`}
            onClick={() => setOpen(!open)}
          >
            <div className="flex gap-1 flex-wrap">
              {selected?.map((item) => (
                <Badge
                  variant="secondary"
                  key={item.id}
                  className="mr-1 mb-1"
                  onClick={() => handleUnselect(item)}
                >
                  {item.value}
                  <button
                    className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                    onKeyDown={(e) => {
                      if (e.key === "Enter") {
                        handleUnselect(item);
                      }
                    }}
                    onMouseDown={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                    }}
                    onClick={() => handleUnselect(item)}
                  >
                    <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                  </button>
                </Badge>
              ))}
            </div>
            <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
          </Button>
        </PopoverTrigger>
        <PopoverContent className="">
          <Command className={className}>
            <CommandInput placeholder="Search..." />
            <CommandEmpty>No item found.</CommandEmpty>
            <CommandGroup className="h-32 overflow-auto">
              {options.map((option) => (
                <CommandItem
                  key={option.id}
                  onSelect={() => {
                    console.log(option.value);
                    console.log(selected);
                    onChange(
                      selected?.some(
                        (item: Record<"id" | "value", string>) =>
                          item.id === option.id
                      )
                        ? selected.filter((item) => item.id !== option.id)
                        : [...selected, option]
                    );
                    setOpen(true);
                  }}
                >
                  <Check
                    className={cn(
                      "mr-2 h-4 w-4",
                      selected?.some((item) => item.id === option.id)
                        ? "opacity-100"
                        : "opacity-0"
                    )}
                  />
                  {option.value}
                </CommandItem>
              ))}
            </CommandGroup>
          </Command>
        </PopoverContent>
      </Popover>
    );
  }
);

MultiSelect.displayName = "MultiSelect";
export { MultiSelect };
``


`

btw, it the `selected` always returned me undefined, and I already check my forms well

@dinogit
Copy link

dinogit commented Oct 23, 2023

import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

import {
  Form,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { MultiSelect } from "@/components/ui/multi-select"
const AuthorsSchema = z.array(
    z.record(
        z.string().trim()
    )
)
const form = useForm<z.infer<typeof AuthorsSchema>>({
    resolver: zodResolver(AuthorsSchema),
    defaultValues: {
      authors: [],
    },
  });
const onHandleSubmit = (values: z.infer<typeof AuthorsSchema>) => {
    console.log({ values })
  };

const authorsData = [
    {
      value: "author1",
      label: "Author 1",
    }, {
      value: "author2",
      label: "Author 2",
    },
    {
      value: "author3",
      label: "Author 3",
    },
    {
      value: "author4",
      label: "Author 4",
    }
  ]
<Form {...form}>
              <form
                onSubmit={form.handleSubmit(onHandleSubmit)}
                className="space-y-4"
              >
                <FormField
                  control={form.control}
                  name="authors"
                  render={({ field: { ...field } }) => (
                    <FormItem className="mb-5">
                      <FormLabel>Author</FormLabel>
                      <MultiSelect
                        selected={field.value}
                        options={authorsData}
                        {...field} />
                    </FormItem>
                  )}
                />
                <Button type="submit" className="w-full">
                  Continue
                </Button>
              </form>
            </Form>

@sersavan
Copy link

sersavan commented Apr 12, 2024

I've assembled a multi-select component using the native shadcn's components. It's fully in line with design and integrates seamlessly into shadcn's ecosystem. Please, try it out and share your thoughts. https://shadcn-multi-select-component.vercel.app/

Hey, yes, that looks good! The previous error was resolved by using divs as buttons, although this approach lacks semantic value. However, it works. Can we improve keyboard accessibility? I couldn't navigate through the component using my keyboard.

to @timwehrle

Hi,
I've finished updating the components to include keyboard support. I've added a handler for some keys:

  • [Enter] for opening a menu and selecting values
  • [Esc] for closing a menu
  • [Backspace] for deleting elements

Try it here: https://shadcn-multi-select-component.vercel.app/

@snufkind
Copy link

@sersavan I can't get your component to work. Options are disabled for some reason for me even though I have identical multi-select component as yours πŸ€”

@sersavan
Copy link

sersavan commented Apr 13, 2024

@sersavan I can't get your component to work. Options are disabled for some reason for me even though I have identical multi-select component as yours πŸ€”

to @snufkind

check "cmdk" version - it should be 0.2.0

image

@sersavan
Copy link

sersavan commented Apr 13, 2024

to @snufkind

I've fixed issue with cmdk, now you can use latest version as well

@joaopedrodcf
Copy link

Really awesome work with this component @sersavan !

I was looking into the aria-selected of each of the CommandItems and seems they are not following what's really selected, as we are controlling in the component the state of each item ourselves.

image

Did you also noticed that ?

@sersavan
Copy link

@joaopedrodcf
It came from cmdk, but in my opinion this "area-select" property doesn't matter much for case of multi-select element as form element
Here mdn's reference (default tor tab):
image

@cbptamtom
Copy link

I've assembled a multi-select component using the native shadcn's components. It's fully in line with design and integrates seamlessly into shadcn's ecosystem. Please, try it out and share your thoughts. https://shadcn-multi-select-component.vercel.app/

Screenshot 2024-04-11 at 00 50 06

Thanks @sersavan. I really like the UI, but I'd prefer to use it with numerical values. Could you assist me with that?

@sersavan
Copy link

I've assembled a multi-select component using the native shadcn's components. It's fully in line with design and integrates seamlessly into shadcn's ecosystem. Please, try it out and share your thoughts. https://shadcn-multi-select-component.vercel.app/
Screenshot 2024-04-11 at 00 50 06

Thanks @sersavan. I really like the UI, but I'd prefer to use it with numerical values. Could you assist me with that?

@cbptamtom
give me an example

@cbptamtom
Copy link

cbptamtom commented Apr 13, 2024

I've assembled a multi-select component using the native shadcn's components. It's fully in line with design and integrates seamlessly into shadcn's ecosystem. Please, try it out and share your thoughts. https://shadcn-multi-select-component.vercel.app/
Screenshot 2024-04-11 at 00 50 06

Thanks @sersavan. I really like the UI, but I'd prefer to use it with numerical values. Could you assist me with that?

@cbptamtom give me an example
Hi @sersavan
I using the type
image

and in my form. I just use warehouse_id is a number array [number]
image

This's the issue in my UI.
image

@sersavan
Copy link

@cbptamtom
this component returns an string[]
so, you can parse it or use z.coerce

@cbptamtom
Copy link

@cbptamtom this component returns an string[] so, you can parse it or use z.coerce

Thanks @sersavan. I've fixed

@snufkind
Copy link

@sersavan You're doing god's work here πŸ˜…

Only thing that would make this absolutely perfect is replacing the breadcrumb's with a text. For example "6 options selected" so that the element doesn't overflow or grow horizontally.

@sersavan
Copy link

sersavan commented Apr 13, 2024

@sersavan You're doing god's work here πŸ˜…

Only thing that would make this absolutely perfect is replacing the breadcrumb's with a text. For example "6 options selected" so that the element doesn't overflow or grow horizontally.

@snufkind
badges - that's the whole point for me))
I'll look for a way to give the user their choice

@santosh-marar
Copy link

@sersavan First thanks. I am unable to add my own style to the component. Can you help me to address this issue.

@sersavan
Copy link

sersavan commented Apr 14, 2024

@sersavan First thanks. I am unable to add my own style to the component. Can you help me to address this issue.

Hi, I've added a few props, including className and variant, as in the native shadcn/ui.
Now you can control the style of the component yourself.
Preset variants: default, secondary, destructive, inverted.
The animation is enabled by adding a prop animation in which you set the animation speed (less -> faster), along with it a magic wand appears so that the user can always turn it off.

image

@zekageri
Copy link

@sersavan First thanks. I am unable to add my own style to the component. Can you help me to address this issue.

Hi, I've added a few props, including className and variant, as in the native shadcn/ui. Now you can control the style of the component yourself. Preset variants: default, secondary, destructive, inverted. The animation is enabled by adding a prop animation in which you set the animation speed (less -> faster), along with it a magic wand appears so that the user can always turn it off.

image

Where does it updated?

@sersavan
Copy link

Where does it updated?

@zekageri
If I understood your question correctly, then you can use this props in the place where you add this component, for example, on the page (like in my repo)

@SwiichyCode
Copy link

SwiichyCode commented Apr 29, 2024

Hello any accessible solution for app-index.js:33 Warning: In HTML, button cannot be a descendant of button.

@AhmedBelkadi
Copy link

<FormField
    control={form.control}
    name="industry"
    render={({ field }) => (
        <FormItem>
            <FormLabel>Select Frameworks</FormLabel>
                <MultiSelect
                    selected={field.value}
                    options={[
                    {
			            value: "next.js",
			            label: "Next.js",
			          },
			          {
			            value: "sveltekit",
			            label: "SvelteKit",
			          },
			          {
			            value: "nuxt.js",
			            label: "Nuxt.js",
			          },
			          {
			            value: "remix",
			            label: "Remix",
			          },
			          {
			            value: "astro",
			            label: "Astro",
			          },
			          {
			            value: "wordpress",
			            label: "WordPress",
			          },
			          {
			            value: "express.js",
			            label: "Express.js",
			          }
                    ]}
                    {...field}
                    className="sm:w-[510px]"
                />
            <FormMessage />
        </FormItem>
    )}
 />

not working !!

Unexpected Application Error!
Cannot read properties of undefined (reading 'length')
TypeError: Cannot read properties of undefined (reading 'length')
at MultiSelect (http://localhost:5173/src/components/ui/MultiSelect.tsx:48:55)
at renderWithHooks (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:11568:26)
at mountIndeterminateComponent (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:14946:21)
at beginWork (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:15934:22)
at beginWork$1 (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:19781:22)
at performUnitOfWork (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:19226:20)
at workLoopSync (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:19165:13)
at renderRootSync (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:19144:15)
at recoverFromConcurrentError (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:18764:28)
at performConcurrentWorkOnRoot (http://localhost:5173/node_modules/.vite/deps/chunk-B27YY5WJ.js?v=0e6ec46c:18712:30)

@sersavan
Copy link

@AhmedBelkadi
Your question is related to what solution?
If with mine repo, then there is a different implementation including other props

@Marco-Antonio-Rodrigues
Copy link

Marco-Antonio-Rodrigues commented May 6, 2024

dinogit Evango None of the components worked in my Next.js application, it renders normally on the screen but when clicked it opens an Error in both:
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))

After a bit of searching I discovered that the problem is in <"CommandItem">, this code renders perfectly, but is not functional:

            <CommandGroup className="h-full overflow-auto">
              {selectables.map((framework) => {
                return (
                <div key={framework.value}>
                    {framework.label}
                </div>
                // <CommandItem
                // key={framework.value}
                // onMouseDown={(e) => {
                // e.preventDefault();
                // e.stopPropagation();
                // }}
                // onSelect={(value) => {
                // setInputValue("")
                // setSelected(prev => [...prev, framework])
                // }}
                // className={"cursor-pointer"}
                // >
                // {framework.label}
                // </CommandItem>
                );
              })}
            </CommandGroup>


any suggestion?πŸ˜„


resolved in this [issues](https://github.com/shadcn-ui/ui/issues/2944)

@joaopedrodcf
Copy link

You need to check the version of the dependencies u have for cmdk as they had a major release, also check if you are importing the components from the right npm package shadcn abstraction instead of directly from radix, it's a really common mistake

@mshahzebraza
Copy link

@sersavan Why have you made the Popover component a controlled one. I've noticed an issue with your deployment that clicking on the trigger-button when the dropdown is opened, causes the dropdown to close and then immediately reopen.

The cause to that is the onInteractOutside handler which is trying to close the popover whenever any element outside the dropdown is clicked. The issue is that, click on the trigger-button is also regarded as an outside click and hence it closes the dropdown in the onInteractOutside handler.

But since the trigger-button itself toggles the dropdown state, we see the dropdown being opened again.

To solve this removing the onInteractOutside handler is a fine solution. I've noticed that none of the logic related to the making the popover a controlled component is needed since all of it is handled by the popover component itself as expected

@sersavan
Copy link

@sersavan Why have you made the Popover component a controlled one. I've noticed an issue with your deployment that clicking on the trigger-button when the dropdown is opened, causes the dropdown to close and then immediately reopen.

The cause to that is the onInteractOutside handler which is trying to close the popover whenever any element outside the dropdown is clicked. The issue is that, click on the trigger-button is also regarded as an outside click and hence it closes the dropdown in the onInteractOutside handler.

But since the trigger-button itself toggles the dropdown state, we see the dropdown being opened again.

To solve this removing the onInteractOutside handler is a fine solution. I've noticed that none of the logic related to the making the popover a controlled component is needed since all of it is handled by the popover component itself as expected

@mshahzebraza
You're right about the behavior of the Popover component and the issue with the onInteractOutside handler. I made the component controlled to avoid the Popover closing when clicking within the component area again. However, I appreciate your suggestion and will consider revisiting this approach to see if there's a better solution.

@mani444
Copy link

mani444 commented May 24, 2024

dinogit Evango None of the components worked in my Next.js application, it renders normally on the screen but when clicked it opens an Error in both: TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))

After a bit of searching I discovered that the problem is in <"CommandItem">, this code renders perfectly, but is not functional:

            <CommandGroup className="h-full overflow-auto">
              {selectables.map((framework) => {
                return (
                <div key={framework.value}>
                    {framework.label}
                </div>
                // <CommandItem
                // key={framework.value}
                // onMouseDown={(e) => {
                // e.preventDefault();
                // e.stopPropagation();
                // }}
                // onSelect={(value) => {
                // setInputValue("")
                // setSelected(prev => [...prev, framework])
                // }}
                // className={"cursor-pointer"}
                // >
                // {framework.label}
                // </CommandItem>
                );
              })}
            </CommandGroup>


any suggestion?πŸ˜„


resolved in this [issues](https://github.com/shadcn-ui/ui/issues/2944)

@Marco-Antonio-Rodrigues you have to wrap 'CommandItem' inside 'CommandList' to make it work in latest version.

  <CommandGroup>
                  <CommandList>
                  {options.map((option) => (
                    <CommandItem
                      key={option.label}
                      value={option.label}
                      onSelect={(currentValue) => {
                        if (onChange) {
                          if (mode === 'multiple' && Array.isArray(selected)) {
                            onChange(
                              selected.includes(option.value)
                                ? selected.filter(
                                    (item) => item !== option.value
                                  )
                                : [...selected, option.value]
                            );
                          } else {
                            onChange(option.value);
                          }
                          setOpen(false);
                        }
                      }}
                    >
                      <Check
                        className={cn(
                          'mr-2 h-4 w-4',
                          selected.includes(option.value)
                            ? 'opacity-100'
                            : 'opacity-0'
                        )}
                      />
                      {option.label}
                    </CommandItem>
                  ))}
                  </CommandList>
                </CommandGroup>

@alamenai
Copy link

alamenai commented May 27, 2024

@shadcn , will we expect to see this component in the next releases? I'm using this MultiSelect component from Tremor, but I would keep my components consistent using only Shadcn UI.

@majames
Copy link

majames commented Jun 22, 2024

Thanks a lot for multi select! One addition that would be great is the ability to turn off searching via an isSearchable prop (similar to react-select). Although i'm not sure how easy that is to add given that it uses a cmdk <Input /> under the hood

https://shadcnui-expansions.typeart.cc/docs/multiple-selector

Edit: Ah... nvm! I can kinda achieve this by passing the following props:

inputProps={{
  inputMode: 'none',
  className: 'caret-transparent', // tailwindcss class, could also use 'style' attribute
}}

@shadcn shadcn added the Stale label Jul 7, 2024
@Jaychandmourya
Copy link

Hello Guys this code multiple select for shadcn-vue with vue3 composition API

<script setup lang="ts">
import { ref } from 'vue'
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover"
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"
  • ref from Vue is used to create reactive references.
  • UI components Button, Badge, Popover, PopoverContent, PopoverTrigger, Command, CommandEmpty, CommandGroup,
    CommandInput, CommandItem, and CommandList are imported from the project structure.
  • cn is a utility function likely used for conditional class names.

// Data

const selectedValue = ref<any>('')
const openMultipleSelect = ref<boolean>(false);
const optionMultipleSelect = ref([
  { label: "Vue3", value: "vue3"},
  { label: "Angular", value: "angular"},
  { label: "React" , value: "react" }
])
  • selectedValue: Stores selected values for the multiple select component.

  • openMultipleSelect: Manages the state of whether the popover is open.

  • optionMultipleSelect: List of selectable options.

// Method

const isSelected = (value: string): boolean => {
  return selectedValue.value?.includes(value);
};

const handleUnselected = (item: string): void => {
  selectedValue.value = selectedValue.value.filter(i => i !== item);
};

const handleOptionSelected = (value: string): void => {
  if (isSelected(value)) {
    selectedValue.value = selectedValue.value.filter(item => item !== value);
  } else {
    selectedValue.value = [...selectedValue.value, value];
  }
  openMultipleSelect.value = true;
};
  • isSelected: Checks if a value is in the selectedValue.
  • handleUnselected: Removes an item from the selectedValue.
  • handleOptionSelected: Adds or removes a value from the selectedValue based on its current state.
<template>
  <div class="grid grid-cols-1 gap-1.5">
    <Label class="text-sm font-medium leading-none mb-0.5">Multiple select</Label>
    <Popover>
      <PopoverTrigger
        as-child
        :class="
          cn(
            'bg-input  text-foreground font-inter hover:bg-input ',
            !selectedValue &&
              'text-inactive-action font-inter hover:text-inactive-action'
          )
        "
      >
        <Button
          variant="secondary"
          size="sm"
          role="combobox"
          class="w-full justify-between"
          :aria-expanded="openMultipleSelect"
        >
          <div class="flex gap-1 flex-wrap">
            <Badge
              v-for="item in selectedValue"
              :key="item"
              variant="rounded"
              color="black"
              class="mr-1"
              @click="handleUnselected(item)"
            >
              {{ item }}
              <button
                class="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
                @keydown.enter.prevent="handleUnselected(item)"
                @mousedown.prevent.stop
                @click.stop="handleUnselected(item)"
              >
                <ph-x-circle class="h-4 w-4" />
              </button>
            </Badge>
          </div>
          <ph-caret-down class="h-4 w-4" />
        </Button>
      </PopoverTrigger>
      <PopoverContent class="w-[570px] p-0">
        <Command>
          <CommandInput placeholder="Search ..." />
          <CommandEmpty>No framework found.</CommandEmpty>
          <CommandList>
            <CommandGroup class="max-h-64 overflow-auto">
              <CommandItem
                v-for="option in optionMultipleSelect"
                :key="option.value"
                :value="option.value"
                @select="
                  handleOptionSelected(option.value)
                "
              >
                <ph-check
                  class="mr-2 h-4 w-4"
                  :class="{
                    'opacity-100': isSelected(option.value),
                    'opacity-0': !isSelected(option.value),
                  }"
                />
                {{ option.label }}
              </CommandItem>
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  </div>
</template>
  • Label: Describes the multiple select input.
  • Popover: A UI component that wraps around the trigger and content for the dropdown.
  • PopoverTrigger: This is the button that, when clicked, opens the popover.
  • Uses the cn function to apply conditional class names.
  • Button: Acts as the main trigger for the popover. Shows the selected items and a down caret icon.
  • Badge: Represents each selected item. Includes a close button to remove the item from the selection.
  • PopoverContent: Contains the content of the dropdown.
  • Command: A component that includes an input for filtering options.
  • CommandInput: Input for searching through the options.
  • CommandEmpty: Message displayed when no options match the search query.
  • CommandList: Contains the list of selectable options.
  • CommandGroup: Wraps around the list of options.
  • CommandItem: Each option in the dropdown. Shows a checkmark if the item is selected.

Summary

The provided code is a Vue 3 Composition API component that implements a custom multiple select input using a popover for the dropdown. The component displays selected items as badges that can be removed. Users can search and select options from the dropdown. The component uses several custom UI components and utility functions to manage its state and display.

@shadcn shadcn removed the Stale label Jul 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests