Skip to main content
Glama
headless-ui.txt•10.3 kB
# Headless UI - Unstyled, Accessible UI Components ## Overview Headless UI provides unstyled, fully accessible UI components designed to integrate with Tailwind CSS. Built by the Tailwind team. ## Installation ```bash npm install @headlessui/react ``` ## Key Components ### Menu (Dropdown) ```tsx import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' function MyDropdown() { return ( <Menu> <MenuButton className="inline-flex items-center gap-2 rounded-md bg-gray-800 py-1.5 px-3 text-sm/6 font-semibold text-white"> Options <ChevronDownIcon className="size-4" /> </MenuButton> <MenuItems className="absolute right-0 mt-2 w-52 origin-top-right rounded-md bg-white shadow-lg"> <MenuItem> {({ focus }) => ( <button className={`${focus ? 'bg-blue-500 text-white' : 'text-gray-900'} group flex w-full items-center px-2 py-2`}> Edit </button> )} </MenuItem> <MenuItem> {({ focus }) => ( <button className={`${focus ? 'bg-blue-500 text-white' : 'text-gray-900'} group flex w-full items-center px-2 py-2`}> Delete </button> )} </MenuItem> </MenuItems> </Menu> ) } ``` ### Dialog (Modal) ```tsx import { Dialog, DialogPanel, DialogTitle } from '@headlessui/react' import { useState } from 'react' function MyModal() { const [isOpen, setIsOpen] = useState(false) return ( <> <button onClick={() => setIsOpen(true)}>Open Modal</button> <Dialog open={isOpen} onClose={() => setIsOpen(false)} className="relative z-50"> {/* Backdrop */} <div className="fixed inset-0 bg-black/30" aria-hidden="true" /> {/* Full-screen container */} <div className="fixed inset-0 flex items-center justify-center p-4"> <DialogPanel className="mx-auto max-w-sm rounded bg-white p-6"> <DialogTitle className="text-lg font-bold">Modal Title</DialogTitle> <p className="mt-2">Modal content goes here</p> <button onClick={() => setIsOpen(false)} className="mt-4">Close</button> </DialogPanel> </div> </Dialog> </> ) } ``` ### Popover ```tsx import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react' function MyPopover() { return ( <Popover className="relative"> <PopoverButton className="text-white/90 focus:outline-none"> Solutions </PopoverButton> <PopoverPanel className="absolute z-10 mt-3 w-screen max-w-sm"> <div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"> <div className="p-4"> <a href="/analytics" className="block rounded p-2 hover:bg-gray-100"> Analytics </a> <a href="/engagement" className="block rounded p-2 hover:bg-gray-100"> Engagement </a> </div> </div> </PopoverPanel> </Popover> ) } ``` ### Listbox (Select) ```tsx import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/react' import { useState } from 'react' const people = [ { id: 1, name: 'Wade Cooper' }, { id: 2, name: 'Arlene Mccoy' }, { id: 3, name: 'Devon Webb' }, ] function MySelect() { const [selected, setSelected] = useState(people[0]) return ( <Listbox value={selected} onChange={setSelected}> <ListboxButton>{selected.name}</ListboxButton> <ListboxOptions> {people.map((person) => ( <ListboxOption key={person.id} value={person} className="cursor-pointer"> {person.name} </ListboxOption> ))} </ListboxOptions> </Listbox> ) } ``` ### Switch (Toggle) ```tsx import { Switch } from '@headlessui/react' import { useState } from 'react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled} className={`${enabled ? 'bg-blue-600' : 'bg-gray-200'} relative inline-flex h-6 w-11 items-center rounded-full`} > <span className={`${enabled ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition`} /> </Switch> ) } ``` ### Tabs ```tsx import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react' function MyTabs() { return ( <TabGroup> <TabList className="flex space-x-1 rounded-xl bg-blue-900/20 p-1"> <Tab className={({ selected }) => `w-full rounded-lg py-2.5 text-sm font-medium ${ selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12]' }` }> Recent </Tab> <Tab className={({ selected }) => `w-full rounded-lg py-2.5 text-sm font-medium ${ selected ? 'bg-white shadow' : 'text-blue-100 hover:bg-white/[0.12]' }` }> Popular </Tab> </TabList> <TabPanels className="mt-2"> <TabPanel>Content 1</TabPanel> <TabPanel>Content 2</TabPanel> </TabPanels> </TabGroup> ) } ``` ### Disclosure (Accordion) ```tsx import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react' import { ChevronUpIcon } from '@heroicons/react/20/solid' function MyAccordion() { return ( <Disclosure> {({ open }) => ( <> <DisclosureButton className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2"> <span>What is your refund policy?</span> <ChevronUpIcon className={`${open ? 'rotate-180 transform' : ''} h-5 w-5`} /> </DisclosureButton> <DisclosurePanel className="px-4 pt-4 pb-2 text-sm text-gray-500"> If you're unhappy with your purchase, we'll refund you in full. </DisclosurePanel> </> )} </Disclosure> ) } ``` ### Combobox (Autocomplete) ```tsx import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption } from '@headlessui/react' import { useState } from 'react' function MyAutocomplete() { const [query, setQuery] = useState('') const [selected, setSelected] = useState(null) const people = [ { id: 1, name: 'Wade Cooper' }, { id: 2, name: 'Arlene Mccoy' }, { id: 3, name: 'Devon Webb' }, ] const filtered = query === '' ? people : people.filter((person) => person.name.toLowerCase().includes(query.toLowerCase()) ) return ( <Combobox value={selected} onChange={setSelected}> <ComboboxInput onChange={(event) => setQuery(event.target.value)} displayValue={(person) => person?.name} /> <ComboboxOptions> {filtered.map((person) => ( <ComboboxOption key={person.id} value={person}> {person.name} </ComboboxOption> ))} </ComboboxOptions> </Combobox> ) } ``` ### Radio Group ```tsx import { RadioGroup, RadioGroupOption } from '@headlessui/react' import { useState } from 'react' const plans = ['Startup', 'Business', 'Enterprise'] function MyRadioGroup() { const [selected, setSelected] = useState(plans[0]) return ( <RadioGroup value={selected} onChange={setSelected}> {plans.map((plan) => ( <RadioGroupOption key={plan} value={plan} className={({ checked }) => `${checked ? 'bg-blue-900 text-white' : 'bg-white'} relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none` }> {plan} </RadioGroupOption> ))} </RadioGroup> ) } ``` ## Transitions ```tsx import { Transition } from '@headlessui/react' import { useState } from 'react' function MyTransition() { const [isShowing, setIsShowing] = useState(false) return ( <> <button onClick={() => setIsShowing(!isShowing)}>Toggle</button> <Transition show={isShowing} enter="transition-opacity duration-300" enterFrom="opacity-0" enterTo="opacity-100" leave="transition-opacity duration-300" leaveFrom="opacity-100" leaveTo="opacity-0" > <div className="p-4 bg-blue-500 text-white rounded"> I will fade in and out </div> </Transition> </> ) } ``` ## Common Patterns ### Mobile Navigation ```tsx <Disclosure as="nav"> {({ open }) => ( <> <DisclosureButton>Menu</DisclosureButton> <DisclosurePanel> <a href="/">Home</a> <a href="/about">About</a> </DisclosurePanel> </> )} </Disclosure> ``` ### Settings Panel ```tsx <Dialog> <DialogPanel> <TabGroup> <TabList> <Tab>General</Tab> <Tab>Privacy</Tab> </TabList> <TabPanels> <TabPanel>General settings</TabPanel> <TabPanel>Privacy settings</TabPanel> </TabPanels> </TabGroup> </DialogPanel> </Dialog> ``` ### Form with Validation ```tsx <Listbox value={selected} onChange={setSelected}> <ListboxButton className="border rounded px-4 py-2"> {selected.name} </ListboxButton> <ListboxOptions> {options.map((option) => ( <ListboxOption key={option.id} value={option}> {({ selected, focus }) => ( <div className={`${focus ? 'bg-blue-100' : ''} ${selected ? 'font-bold' : ''}`}> {option.name} </div> )} </ListboxOption> ))} </ListboxOptions> </Listbox> ``` ## Best Practices 1. **Use render props** for dynamic styling based on state 2. **Combine with Tailwind** for easy styling 3. **Accessible by default** - includes proper ARIA attributes 4. **Keyboard navigation** - built-in keyboard support 5. **Focus management** - automatic focus handling ## With TypeScript ```tsx import { Dialog } from '@headlessui/react' interface MyModalProps { isOpen: boolean onClose: () => void title: string } function MyModal({ isOpen, onClose, title }: MyModalProps) { return ( <Dialog open={isOpen} onClose={onClose}> {/* Modal content */} </Dialog> ) } ``` ## Resources - Docs: https://headlessui.com/ - Examples: https://headlessui.com/react/menu#examples - GitHub: https://github.com/tailwindlabs/headlessui

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CaullenOmdahl/Nextjs-React-Tailwind-Assistant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server