Headless UI v1.0
- Date
- Adam Wathan
Last fall we announced Headless UI, a library of completely unstyled, fully accessible UI components, designed to pair perfectly with Tailwind CSS.
Today we’re super excited to release Headless UI v1.0, which more than doubles the amount of included components for both React and Vue.
What’s new
We’ve added four new components to the React library, and five new components for Vue.
Dialog (modal)
Headless UI now includes a robust dialog implementation you can use to build traditional modal dialogs, mobile slide-out menus, or any other take-over-style UI that needs to capture the focus of the entire page.
import { useState } from 'react'
import { Dialog } from '@headlessui/react'
function MyDialog() {
let [isOpen, setIsOpen] = useState(true)
return (
<Dialog open={isOpen} onClose={setIsOpen}>
<Dialog.Overlay />
<Dialog.Title>Deactivate account</Dialog.Title>
<Dialog.Description>
This will permanently deactivate your account
</Dialog.Description>
<p>
Are you sure you want to deactivate your account? All of your data will
be permanently removed. This action cannot be undone.
</p>
<button onClick={() => setIsOpen(false)}>Deactivate</button>
<button onClick={() => setIsOpen(false)}>Cancel</button>
</Dialog>
)
}
Disclosure
We’ve added a new Disclosure
component that makes it easy to show/hide inline content accessibly. This is useful for things like collapsible FAQ questions, “show more” interfaces, or even hamburger menus that open up and push the rest of the page content away.
<template>
<Disclosure>
<DisclosureButton> Is team pricing available? </DisclosureButton>
<DisclosurePanel>
Yes! You can purchase a license that you can share with your entire team.
</DisclosurePanel>
</Disclosure>
</template>
<script>
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from '@headlessui/vue'
export default {
components: { Disclosure, DisclosureButton, DisclosurePanel },
}
</script>
Radio Group
There’s now a RadioGroup
component that you can use to build totally custom radio button UIs, like when you want to use fancy cards or something instead of a simple little radio circle.
import { useState } from 'react'
import { RadioGroup } from '@headlessui/react'
function MyRadioGroup() {
let [plan, setPlan] = useState('startup')
return (
<RadioGroup value={plan} onChange={setPlan}>
<RadioGroup.Label>Plan</RadioGroup.Label>
<RadioGroup.Option value="startup">
{({ checked }) => (
<span className={checked ? 'bg-blue-200' : ''}>Startup</span>
)}
</RadioGroup.Option>
<RadioGroup.Option value="business">
{({ checked }) => (
<span className={checked ? 'bg-blue-200' : ''}>Business</span>
)}
</RadioGroup.Option>
<RadioGroup.Option value="enterprise">
{({ checked }) => (
<span className={checked ? 'bg-blue-200' : ''}>Enterprise</span>
)}
</RadioGroup.Option>
</RadioGroup>
)
}
Popover
The new Popover
component lets you build custom dropdown UIs that don’t have any content restrictions like a regular Menu
component would. Great for fly-out menus on marketing sites, dropdowns that have form fields in them, and tons more.
<template>
<Popover class="relative">
<PopoverButton>Solutions</PopoverButton>
<PopoverPanel class="absolute z-10">
<div>
<a href="/analytics">Analytics</a>
<a href="/engagement">Engagement</a>
<a href="/security">Security</a>
<a href="/integrations">Integrations</a>
</div>
<img src="/solutions.jpg" alt="" />
</PopoverPanel>
</Popover>
</template>
<script>
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
export default {
components: { Popover, PopoverButton, PopoverPanel },
}
</script>
TransitionRoot and TransitionChild (for Vue)
Headless UI already had a Transition
component for React, but we’ve always recommended the native <transition>
that already ships with Vue for Vue users. There are some limitations to the native transition though, and things can get complicated when trying to co-ordinate nested transitions that are supposed to run in parallel.
Headless UI v1.0 brings our React Transition
component to Vue as well, which makes it a lot easier to transition things like modal dialogs.
<template>
<!— This `show` prop controls all nested `Transition.Child` components. —>
<TransitionRoot :show="isOpen">
<!— Background overlay —>
<TransitionChild
enter="transition-opacity"
ease-linear
duration-300"
enter-from="opacity-0"
enter-to="opacity-100"
leave="transition-opacity"
ease-linear
duration-300"
leave-from="opacity-100"
leave-to="opacity-0"
>
<!— … —>
</TransitionChild>
<!— Sliding sidebar —>
<TransitionChild
enter="transition"
ease-in-out
duration-300
transform"
enter-from="-translate-x-full"
enter-to="translate-x-0"
leave="transition"
ease-in-out
duration-300
transform"
leave-from="translate-x-0"
leave-to="-translate-x-full"
>
<!— … —>
</TransitionChild>
</TransitionRoot>
</template>
<script>
import { ref } from "vue";
import { Transition, TransitionChild } from "@headlessui/vue";
export default {
components: { TransitionRoot: Transition, TransitionChild },
setup() {
const isShowing = ref(true);
return {
isShowing,
};
},
};
</script>
Try it out
Head over to our brand new documentation website to pull Headless UI into your projects and play with it! It’s MIT licensed and open-source, so if you’d like to poke around the code or you need to report an issue, visit the GitHub repository.
Want to try it out? Visit the Headless UI website →