Installation
Copy the files, install the dependencies, wire your router. Ten minutes, once.
Before you start
You need a project running Tailwind CSS v4. The components are plain components of your framework: Next.js, Remix, Inertia, Nuxt or any Vite setup all work, nothing is tied to a router or a build tool.
If Tailwind CSS is not set up yet, follow the framework guide in the Tailwind CSS documentation first, then come back here.
Get the components
There is nothing to download. Each component page opens with an Installation section: the file, or the few files, the component is made of, each with a copy button. Copy them into the folder where you keep your own components, components/ for example, so imports read @/components/button.
Take one component or the whole kit: a component only imports its siblings, never a hidden module, and its page says which siblings it needs. The files are yours from here, rename them, restyle them, delete what you do not use. The examples of this documentation copy the same way, so a screen starts from an example, not from a blank file.
Install the dependencies
The React kit relies on Headless UI for accessible behavior, on Motion for the few animations, on clsx to merge class names and on Heroicons for the chevrons and checkmarks drawn inside the components.The Vue kit relies on Reka UI for accessible behavior, on Motion for Vue for the few animations and on Heroicons for the chevrons and checkmarks drawn inside the components. The command follows the framework chosen in the header:
npm install @headlessui/react motion clsx @heroicons/reactpnpm add @headlessui/react motion clsx @heroicons/reactyarn add @headlessui/react motion clsx @heroicons/reactbun add @headlessui/react motion clsx @heroicons/reactnpm install reka-ui motion-v @heroicons/vuepnpm add reka-ui motion-v @heroicons/vueyarn add reka-ui motion-v @heroicons/vuebun add reka-ui motion-v @heroicons/vueA few components need one more package, the date pickers, the phone input or the toasts for example. Their page says which, right above the first example.
Use a component
Import each component from where you copied it and use it like any component you wrote:
import { Button } from '@/components/button'
import { Field, FieldGroup, Label } from '@/components/fieldset'
import { Input } from '@/components/input'
export default function SettingsForm() {
return (
<form>
<FieldGroup>
<Field>
<Label>Name</Label>
<Input name="name" />
</Field>
<Field>
<Label>Email</Label>
<Input type="email" name="email" />
</Field>
<Button type="submit">Save changes</Button>
</FieldGroup>
</form>
)
}<script setup lang="ts">
import Button from '@/components/button.vue'
import Field from '@/components/field.vue'
import FieldGroup from '@/components/field-group.vue'
import Input from '@/components/input.vue'
import Label from '@/components/label.vue'
</script>
<template>
<form>
<FieldGroup>
<Field>
<Label>Name</Label>
<Input name="name" />
</Field>
<Field>
<Label>Email</Label>
<Input type="email" name="email" />
</Field>
<Button type="submit">Save changes</Button>
</FieldGroup>
</form>
</template>Router integration
Everything that renders a link (Button with href, Link, SidebarItem, …) goes through one Link component, so your router is wired in one place. Open link.tsx and swap the anchor for the link of your router. With Next.js:Hand the link of your router to the kit once, in a plugin, and link.vue stays untouched. With Nuxt:
// components/link.tsx
import * as Headless from '@headlessui/react'
import NextLink, { type LinkProps } from 'next/link'
import React, { forwardRef } from 'react'
export const Link = forwardRef(function Link(
props: LinkProps & React.ComponentPropsWithoutRef<'a'>,
ref: React.ForwardedRef<HTMLAnchorElement>
) {
return (
<Headless.DataInteractive>
<NextLink {...props} ref={ref} />
</Headless.DataInteractive>
)
})// plugins/trelund-link.ts
import { LinkComponentKey } from '@/components/link.vue'
import { NuxtLink } from '#components'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.provide(LinkComponentKey, NuxtLink)
})Same idea with Inertia.js:
// components/link.tsx
import * as Headless from '@headlessui/react'
import { Link as InertiaLink } from '@inertiajs/react'
import React, { forwardRef } from 'react'
export const Link = forwardRef(function Link(
props: { href: string } & React.ComponentPropsWithoutRef<'a'>,
ref: React.ForwardedRef<HTMLAnchorElement>
) {
return (
<Headless.DataInteractive>
<InertiaLink {...props} ref={ref} />
</Headless.DataInteractive>
)
})// app.ts
import { LinkComponentKey } from '@/components/link.vue'
import { createInertiaApp, Link } from '@inertiajs/vue3'
import { createApp, h } from 'vue'
createInertiaApp({
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.provide(LinkComponentKey, Link)
.mount(el)
},
})Left untouched, Link renders a plain <a> tag, which is all a standard Vite application needs.
Icons
The examples use Heroicons, whose icons carry data-slot="icon": buttons, dropdowns and sidebars size and color them for you. Any SVG with that attribute gets the same treatment.
import { Button } from '@/components/button'
import { PlusIcon } from '@heroicons/react/16/solid'
export default function AddItemButton() {
return (
<Button>
<PlusIcon />
Add item
</Button>
)
}<script setup lang="ts">
import Button from '@/components/button.vue'
import { PlusIcon } from '@heroicons/vue/16/solid'
</script>
<template>
<Button>
<PlusIcon />
Add item
</Button>
</template>Next step
The components ship in blue. Set your accent before you build your first screen.