Utilities and variants for styling Base UI state
Important
This is still a WIP, most of the code was ported with Cursor from tailwindcss-radix
The main purpose of this library is adding classnames for accessing Base UI data attributes, which gains you the benefit of auto-completion compared to using data-*
variants.
TL;DR It's @headlessui-tailwindcss for Base UI.
pnpm add tailwindcss-base-ui
Click on the banner to check out the demo components. You can find the code inside the demo folder.
With @plugin directive (recommended)
Default prefix
/* Generates `bui-*` utilities for data attributes */
@plugin "tailwindcss-base-ui";
Custom prefix
/* Generates `ui-*` utilities for data attributes */
@plugin "tailwindcss-base-ui" {
variantPrefix: ui;
}
With @config directive
Default prefix
module.exports = {
// --snip --
plugins: [
// Generates `bui-*` utilities for data attributes
require("tailwindcss-base-ui")(),
],
};
Custom prefix
module.exports = {
// --snip --
plugins: [
// Generates `ui-*` utilities for data attributes
require("tailwindcss-base-ui")({
variantPrefix: "ui",
}),
],
};
Load configuration
@config "../../tailwind.config.js";
This plugin works with CSS attribute selectors. Use the variants based on the data-*
attribute added by Base UI.
import React from "react";
import * as DropdownMenuPrimitive from "@base-ui-ui/react-dropdown-menu";
const App = () => {
return (
<DropdownMenuPrimitive.Root>
<DropdownMenuPrimitive.Trigger className="border-black bui-ui-state-open:border-2">
Trigger
</DropdownMenuPrimitive.Trigger>
<DropdownMenuPrimitive.Content>
<DropdownMenuPrimitive.Item>Item</DropdownMenuPrimitive.Item>
</DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Root>
);
};
export default App;
When you need to style an element based on the state of a parent element, mark the parent with the group
class and style the target with group-bui-*
modifiers.
Example usage of a conditional transform for a Base UI Accordion
:
import React from "react";
import * as AccordionPrimitive from "@base-ui-ui/react-accordion";
import { ChevronDownIcon } from "@base-ui-ui/react-icons";
const Accordion = () => {
return (
<AccordionPrimitive.Root type="multiple">
<AccordionPrimitive.Item value="item-1">
<AccordionPrimitive.Header>
<AccordionPrimitive.Trigger className="group">
<div className="flex items-center">
Item 1
<ChevronDownIcon className="w-5 h-5 ml-2 transform group-bui-open:rotate-180" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
<AccordionPrimitive.Content>Content 1</AccordionPrimitive.Content>
</AccordionPrimitive.Item>
<AccordionPrimitive.Item value="item-2">
<AccordionPrimitive.Header>
<AccordionPrimitive.Trigger className="group">
<div className="flex items-center">
Item 2
<ChevronDownIcon className="w-5 h-5 ml-2 transform group-bui-state-open:rotate-180" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
<AccordionPrimitive.Content>Content 2</AccordionPrimitive.Content>
</AccordionPrimitive.Item>
</AccordionPrimitive.Root>
);
};
export default App;
When you need to style an element based on the state of a sibling element, mark the sibling with the peer
class and style the target with peer-bui-*
modifiers.
Example usage of a conditional icon color for a sibling of a Base UI Checkbox
:
import * as CheckboxPrimitive from "@base-ui-ui/react-checkbox";
import { CheckIcon, TargetIcon } from "@base-ui-ui/react-icons";
import React from "react";
interface Props {}
const App = (props: Props) => {
return (
<>
<CheckboxPrimitive.Root id="c1" defaultChecked className="peer h-5 w-5">
<CheckboxPrimitive.Indicator>
<CheckIcon />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
<TargetIcon className="text-red-500 peer-bui-checked:text-green-500" />
</>
);
};
export default App;
Use the generated disabled
variant.
import React from "react";
import * as ContextMenuPrimitive from "@base-ui-ui/react-context-menu";
const ContextMenu = () => {
return (
// --snip--
<ContextMenuPrimitive.Item
disabled
className="bui-disabled:opacity-50 bui-disabled:cursor-not-allowed"
>
Item
</ContextMenuPrimitive.Item>
// --snip--
);
};