I love using data attributes for this sort of thing, find the code reads a lot more like standard Tailwind stuff compared to a bunch of ternaries and conditional logic in JavaScript.
\\nThere\'s a section in Compass for interviews, which each include a video at the top of the page but also a pretty custom transcript UI below:
\\nAt first we weren\'t totally sure how we should approach this. Our first idea was to just write some React components for these pieces and throw the content directly into an MDX file, but it felt sort of unrealistic to expect anyone to ever author their transcripts in this totally custom format.
\\nThen I had the idea to use a standard caption file format, like SRT. The problem was that for our needs, we needed to encode the current speaker (Tom or Annie in the screenshot above), and SRT has no standardized way of encoding that information in the file.
\\nI did some more research and eventually landed on WebVTT, which is similar to SRT but does support speaker information, and is conveniently designed for the web.
\\nWEBVTT00:00.000 --\x3e 00:20.000<v Tom Harris>Hello fellow passengers, welcome to the Compass podcast. Today, we have a special guest, Annie King. She\'s the author of The Inevitable You: How to Embrace Your Path and Succeed with Relentless Precision. Annie, welcome to the show.00:20.000 --\x3e 00:35.000<v Annie King>Thank you! I\'m so happy to be here. And thanks for sending me the questions in advance — I\'m really excited to share some of the ideas from the book with your viewers. I think we\'re going to have a lot of fun unpacking what it means to truly embrace your path.00:35.000 --\x3e 00:45.000<v Tom Harris>Absolutely! I want to get into your book, but first I have to ask — what was it like growing up in a household that treated organization almost like...a sport?
So we wired things up to parse the transcript data from a .vtt
file, and then map over that data to render it as a custom UI with React:
<div> {transcript.map(({ start, speaker, text }) => ( <div key={start} className=\\"col-span-2 grid grid-cols-subgrid items-baseline\\"> <TimestampButton start={start} videoId=\\"video\\" className=\\"justify-self-end\\" /> <div> <p className=\\"text-sm/7 font-semibold text-gray-950 dark:text-white\\">{speaker}</p> {text.map((p, index) => ( <p key={index} className=\\"mt-2 text-sm/7 whitespace-pre-wrap text-gray-700 dark:text-gray-400\\"> {p} </p> ))} </div> </div> ))}</div>
Turned out pretty cool — I can imagine someone just generating a transcript in VTT format using AI, throwing it into the project and it\'ll automatically be rendered in a nice custom UI.
\\nThe example content we came up with for Compass includes a lot of diagrams that we wanted to adapt between light and dark mode.
\\nYou can do this sort of thing a ton of different ways (the <picture>
tag supports it natively for example), but we really wanted to keep the content files feeling as much like vanilla Markdown as possible.
So we came up with this idea of a {scheme}
placeholder in the image URL that we replace with light
or dark
dynamically, loading the correct image based on the user\'s color scheme:
## The Myth of Free WillYour brain makes decisions before you are even aware of them.
Now the underlying image component will automatically render neuro-proof.light.png
or neuro-proof.dark.png
depending on the current color scheme. And if you just want to use the same image in both, just don\'t include {scheme}
in the URL at all.
We also wanted to avoid layout shift in the content, which you can do pretty easily these days by ensuring you give your images width
and height
attributes so the browser can compute the aspect ratio and reserve space for the image when it loads.
There\'s no standard way to express this in Markdown\'s image syntax, but after a bit of research we discovered some precedent in Obsidian.
\\nObsidian tacks the image dimensions on to the alt text, like this:
\\n## The Myth of Free WillYour brain makes decisions before you are even aware of them.
So we used a custom MDX component to pull out the dimensions using the same format, and add those to the image to make sure there\'s no layout shift as the content loads.
\\nSo there you go — that\'s Compass! As always it\'s a free update for anyone with a Tailwind Plus license, so go and download the codebase, poke around, and have fun with it.
\\nIf you don\'t already have a Tailwind Plus license, think about getting one! Picking one up is the best way to support our work on Tailwind CSS and there\'s a ton of useful stuff in there.
\\nLooking forward to using this template for some upcoming projects myself in the next couple of months!
","description":"I\'ve been itching to get back into screencasting and teaching lately, so a couple of months ago I put together this free Build UIs that don\'t suck mini-course. People seemed to like it and now I want to do some bigger video projects, but there was a problem. I am a software…","guid":"Compass: A starter kit for online courses","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2025-05-14T19:00:00.963Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.74265ef8.jpg","type":"photo","width":2400,"height":1260,"blurhash":"LEQm6Z_2oy?bDN%2M_t7ozX8WCRj"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fpreview-01.20486579.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://assets.tailwindcss.com/blog/2025-05-14-compass-course-starter-kit/compass-pip-demo.mp4","type":"video"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftranscript.f0edcebf.png&w=3840&q=75","type":"photo","width":1492,"height":1142,"blurhash":"LCSY{r_3xu~q~qRjRjWV%MRjRjRj"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flight-dark-diagram.206982cb.png&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Tailwind CSS v4.1: Text shadows, masks, and tons more","url":"https://tailwindcss.com/blog/tailwindcss-v4-1","content":"I wasn\'t sure it would ever happen but we did it — we released a version of Tailwind CSS that includes text-shadow
utilities.
Tailwind CSS v4.1 is here and it\'s packed with new utilities, variants, and developer experience improvements that will help you (or your LLM, you coward) build even better interactive experiences.
\\nHere\'s all the best stuff we got into this release:
\\ntext-shadow-*
utilities — only about twenty years after they were first supported by a browser.mask-*
utilities — use images and gradients to mask elements with new ergonomic APIs.overflow-wrap
— defend the integrity of your layouts from even the longest German words your users will throw at you.drop-shadow
support — can\'t really remember why we didn\'t have these before but we do now.pointer-*
and any-pointer-*
— tweak your design for touch devices explicitly instead of relying on viewport size.items-baseline-last
and self-baseline-last
utilities.safe
alignment — center content in flex and grid layouts without it disappearing when there\'s not enough space.@source not
— explicitly ignore irrelevant large directories and speed up your builds even more.@source inline(…)
— force Tailwind to include classes that aren\'t in your source files.noscript
, user-valid
, inverted-colors
, and more.That\'s all the cool stuff, but there\'s a few other little things hiding in the release notes that you might want to check out too.
\\nUpgrade your projects by installing the latest version of tailwindcss
from npm:
npm install tailwindcss@latest @tailwindcss/cli@latest
npm install tailwindcss@latest @tailwindcss/vite@latest
npm install tailwindcss@latest @tailwindcss/postcss@latest
text-shadow-*
utilitiesWe\'ve been threatening to add text shadows for at least the last six years and today they are finally here.
\\nWe\'ve added five text shadows to the default theme, from text-shadow-2xs
to text-shadow-lg
. They are particularly useful for making headings stand out against a busy background:
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
<p class=\\"text-shadow-2xs ...\\">The quick brown fox...</p><p class=\\"text-shadow-xs ...\\">The quick brown fox...</p><p class=\\"text-shadow-sm ...\\">The quick brown fox...</p><p class=\\"text-shadow-md ...\\">The quick brown fox...</p><p class=\\"text-shadow-lg ...\\">The quick brown fox...</p>
You can change the color of the shadow using the text-shadow-<color>
utilities. For instance, you can create a sort of embossed effect by using a small white shadow on dark text:
<button class=\\"text-sky-950 text-shadow-2xs text-shadow-sky-300 ...\\">Book a demo</button><button class=\\"text-gray-950 dark:text-white dark:text-shadow-2xs ...\\">See pricing</button>
If you just want to adjust the opacity of a text shadow without changing the color, you can slap an opacity modifier directly on text shadow size utilities like text-shadow-lg
.
For example, text-shadow-lg/50
is the same as setting text-shadow-lg
and text-shadow-black/50
at the same time:
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
<p class=\\"text-shadow-lg ...\\">The quick brown fox...</p><p class=\\"text-shadow-lg/20 ...\\">The quick brown fox...</p><p class=\\"text-shadow-lg/30 ...\\">The quick brown fox...</p>
Check out the text-shadow
docs for more details.
mask-*
utilitiesOne of the coolest features of modern CSS is the ability to use images and gradients as masks - basically using the opacity of an image to hide certain parts of an element:
\\nSpeed
Built for power users
Work faster than ever with our keyboard shortcuts
<div class=\\"mx-auto flex items-center p-16 max-sm:p-8\\"> <img src=\\"/img/keyboard.png\\" class=\\"mask-radial-from-transparent mask-radial-from-15% mask-radial-to-black mask-radial-to-55% mask-radial-at-right ...\\" /> <div class=\\"font-medium\\"> <p class=\\"font-mono text-xs text-blue-500 uppercase dark:text-blue-400\\">Speed</p> <p class=\\"mt-2 text-base text-gray-700 dark:text-gray-300\\">Built for power users</p> <p class=\\"mt-1 text-sm leading-relaxed text-balance text-gray-500\\"> Work faster than ever with our keyboard shortcuts </p> </div></div>
Because you can use any background-image
as a mask, the logical thing to do was to copy the bg-*
utilities so they share the same API. The problem with that approach is you often want to combine multiple masks together and the bg-*
utilities are not composable.
So instead, we created a new set of utilities to work with mask-image
that are composable and purpose-built for the masking use case. For example, you can use utilities like mask-b-from-<value>
and mask-t-to-<value>
to add a linear gradient mask to a single side of an element:
mask-t-from-50%
mask-r-from-30%
mask-l-from-50% mask-l-to-90%
mask-b-from-20% mask-b-to-80%
<div class=\\"mask-t-from-50% bg-[url(/img/mountains.jpg)] ...\\"></div><div class=\\"mask-r-from-30% bg-[url(/img/mountains.jpg)] ...\\"></div><div class=\\"mask-l-from-50% mask-l-to-90% bg-[url(/img/mountains.jpg)] ...\\"></div><div class=\\"mask-b-from-20% mask-b-to-80% bg-[url(/img/mountains.jpg)] ...\\"></div>
It\'s more natural to think about which side you want to mask, rather than trying to work out the exact gradient you need to use.
\\nThe gradient mask utilities are also composable, so you can combine radial, conic and linear gradients together to create more complex masks:
\\n<div class=\\"mask-b-from-50% mask-radial-[50%_90%] mask-radial-from-80% bg-[url(/img/mountains.jpg)] ...\\"></div><div class=\\"mask-r-from-80% mask-b-from-80% mask-radial-from-70% mask-radial-to-85% bg-[url(/img/mountains.jpg)] ...\\"></div>
Masking is a super powerful technique and there\'s a lot more to the API than we can cover here. For a full breakdown of the new utilities, check out the documentation.
\\nWe went all-in on modern platform features with Tailwind CSS v4.0 to make the best framework we could, and give this version the longest shelf-life possible.
\\nUnfortunately some of those features degrade really poorly in older browsers, to the point where even basic things like colors and shadows might not render at all for someone visiting from an old iPhone or iPad that\'s stuck on Safari 15.
\\nFor Tailwind CSS v4.1, we put a bunch of effort into coming up with and testing our own framework-specific fallbacks to make your sites render as best as possible in older browsers, even if some super modern things still don\'t behave quite the same.
\\nHere\'s a list of the things we\'ve managed to improve in this release:
\\noklab
now render in older versions of Safari@property
(like shadows, transforms, gradients and more) now work in older versions of Safari and FirefoxTailwind CSS v4 is still designed for modern browsers like Safari 16.4 and up and still depends on a lot of modern features for everything to work perfectly, but at least now your sites will render and be usable in older browsers, even if in certain specific situations the odd shadow color be different.
\\nTo learn everything you need to know about browser compatibility in Tailwind CSS v4, you can read the full browser compatibility documentation.
\\noverflow-wrap
The new overflow-wrap
utilities let you control how text wraps within an element. The wrap-break-word
utility is especially useful for long words or URLs that might otherwise break your layout:
The longest word in any of the major English language dictionaries is pneumonoultramicroscopicsilicovolcanoconiosis, a word that refers to a lung disease contracted from the inhalation of very fine silica particles, specifically from a volcano; medically, it is the same as silicosis.
<p class=\\"wrap-break-word\\">The longest word in any of the major...</p>
The one case where this doesn\'t quite behave like you\'d expect is inside a flex container and that\'s where you probably want to use the new wrap-anywhere
utility instead.
It\'s similar to wrap-break-word
, but it allows mid-word line breaks when calculating the intrinsic size of the element, replacing the need to set min-width: 0
on the child element:
wrap-break-word
Jay Riemenschneider
jason.riemenschneider@vandelayindustries.com
wrap-anywhere
Jay Riemenschneider
jason.riemenschneider@vandelayindustries.com
<div class=\\"flex max-w-sm\\"> <img class=\\"size-16 rounded-full\\" src=\\"/img/profile.jpg\\" /> <div class=\\"wrap-break-word\\"> <p class=\\"font-medium\\">Jay Riemenschneider</p> <p>jason.riemenschneider@vandelayindustries.com</p> </div></div><div class=\\"flex max-w-sm\\"> <img class=\\"size-16 rounded-full\\" src=\\"/img/profile.jpg\\" /> <div class=\\"wrap-anywhere\\"> <p class=\\"font-medium\\">Jay Riemenschneider</p> <p>jason.riemenschneider@vandelayindustries.com</p> </div></div>
There\'s not much more to it than that, but here\'s the overflow-wrap
documentation if you want to read it again in slightly different words.
drop-shadow
supportWhile we were building out text-shadow
support we thought we might as well add another feature we never got around to implementing: colored drop shadows.
Now you can use utilities like drop-shadow-indigo-500
and drop-shadow-cyan-500/50
to change the color of a drop shadow:
drop-shadow-cyan-500/50
drop-shadow-sky-500/50
drop-shadow-indigo-500/50
<svg class=\\"fill-cyan-500 drop-shadow-xl drop-shadow-cyan-500/50 ...\\">...</svg><svg class=\\"fill-blue-500 drop-shadow-xl drop-shadow-blue-500/50 ...\\">...</svg><svg class=\\"fill-indigo-500 drop-shadow-xl drop-shadow-indigo-500/50 ...\\">...</svg>
There isn\'t much more to it but here\'s the drop-shadow
documentation anyway.
pointer-*
and any-pointer-*
The new pointer-fine
and pointer-coarse
variants let you style something differently depending on whether the user is using a device with a mouse or using a touchscreen.
Use pointer-fine
to target precise pointing devices like mouses and trackpads, and pointer-coarse
to target devices lower precision like touchscreens:
Try emulating a touch device in your developer tools to see the changes
<fieldset aria-label=\\"Choose a memory option\\"> <div class=\\"flex items-center justify-between\\"> <div>RAM</div> <a href=\\"#\\"> See performance specs </a> </div> <div class=\\"mt-4 grid grid-cols-6 gap-2 pointer-coarse:mt-6 pointer-coarse:grid-cols-3 pointer-coarse:gap-4\\"> <label class=\\"p-2 pointer-coarse:p-4 ...\\"> <input type=\\"radio\\" name=\\"memory-option\\" value=\\"4 GB\\" className=\\"sr-only\\" /> <span>4 GB</span> </label> <!-- ... --\x3e </div></fieldset>
If you\'re on your phone, you\'ll see the pointer-coarse
styles applied, which make the touch targets larger and easier to hit. If you\'re on a desktop, you\'ll see the pointer-fine
styles applied, which make the touch targets smaller and more precise.
The any-pointer-*
variants work the same way but instead of just checking the user\'s primary pointing device, they check if any pointing device matches. So any-pointer-coarse
will match on a laptop with a touchscreen for a example, even if the user also has a mouse connected.
When working with flex or grid layouts, sometimes you need to align something to the baseline of the last line of text rather than the end of the container.
\\nThe new items-baseline-last
utility does just that:
Working on the future of astronaut recruitment at Space Recruit.
A multidisciplinary designer.
<div class=\\"grid grid-cols-[1fr_auto] items-baseline-last\\"> <div> <img src=\\"img/spencer-sharp.jpg\\" /> <h4>Spencer Sharp</h4> <p>Working on the future of astronaut recruitment at Space Recruit.</p> </div> <p>spacerecruit.com</p></div>
We\'ve also added self-baseline-last
for when you need to align just a single item, and not all items in the flex or grid container.
safe
alignmentEver had center aligned content overflow in both directions when the container got too small? Now you don\'t have to use a container query to switch the alignment at different sizes.
\\nThe new safe
alignment utilities will change the alignment to start
when the container starts to overflow, so it only overflows in one direction.
Resize the container to see the alignment behavior
justify-center
justify-center-safe
<ul class=\\"flex justify-center gap-2 ...\\"> <li>Sales</li> <li>Marketing</li> <li>SEO</li> <!-- ... --\x3e</ul>
<ul class=\\"flex justify-center-safe gap-2 ...\\"> <li>Sales</li> <li>Marketing</li> <li>SEO</li> <!-- ... --\x3e</ul>
These utilities work with both flexbox and grid layouts, and are available for all alignment properties.
\\n@source not
Sometimes you need to specifically exclude some parts of your code base from being scanned by Tailwind. Now you can use @source not
to ignore specific paths when scanning for class names:
@import \\"tailwindcss\\";@source not \\"./src/components/legacy\\";
This is useful when you have a large number of files in your project, but only want to scan a specific subset of them.
\\n@source inline(…)
If you need to make sure Tailwind generates certain class names that don’t exist in your content files, you can force them to be generated by using @source inline()
:
@import \\"tailwindcss\\";@source inline(\\"underline\\");
.underline { text-decoration: underline;}
This is the equivalent of the safelist
configuration option in previous versions of Tailwind, but now you can use it in your CSS files instead of your config file.
The source input is brace-expanded, so you can generate multiple classes at once. For example, to generate all the red shades with hover variants, you can add a range to the source input:
\\n@import \\"tailwindcss\\";@source inline(\\"{hover:,}bg-red-{50,{100..900..100},950}\\");
.bg-red-50 { background-color: var(--color-red-50);}.bg-red-100 { background-color: var(--color-red-100);}.bg-red-200 { background-color: var(--color-red-200);}/* ... */.bg-red-800 { background-color: var(--color-red-800);}.bg-red-900 { background-color: var(--color-red-900);}.bg-red-950 { background-color: var(--color-red-950);}@media (hover: hover) { .hover\\\\:bg-red-50:hover { background-color: var(--color-red-50); } /* ... */ .hover\\\\:bg-red-950:hover { background-color: var(--color-red-950); }}
This will generate shades of red from 100 to 900 in increments of 100, as well as the 50 and 950 shades. It also adds the hover:
variant for each of those classes.
You can also use @source inline()
with the not
modifier to exclude specific classes from being generated:
@import \\"tailwindcss\\";@source not inline(\\"container\\");
This will specifically prevent the container
class from being generated, even if the word container
is detected in your source files.
For more details, check out the detecting classes in source files documentation.
\\ndetails-content
While you could always add styles to the children of a <details>
element, it\'s been impossible to style the content container itself.
The new details-content
variant targets the content container which is useful for positioning the content container relative to the <summary>
element:
The mug is round. The jar is round. They should call it Roundtine.
<details class=\\"rounded-lg border border-transparent p-6 details-content:mt-3 details-content:-ml-0.5\\" open> <summary class=\\"text-sm leading-6 font-semibold text-gray-900 select-none dark:text-white\\"> Why do they call it Ovaltine? </summary> <div class=\\"border-gray-200 bg-gray-50 py-3 pl-3 dark:border-white/10 dark:bg-gray-800/50 ...\\"> <p>The mug is round. The jar is round. They should call it Roundtine.</p> </div></details>
inverted-colors
modeUse the inverted-colors
variant to conditionally add styles when the user has enabled an inverted color scheme in their OS:
<div class=\\"shadow-xl inverted-colors:shadow-none ...\\"> <!-- ... --\x3e</div>
This is useful for things like preventing black shadows being turned white when inverted colors are enabled.
\\nnoscript
variantYes, some people disable JavaScript and now you can tell them your app doesn\'t work without it. The noscript
variant lets you conditionally apply styles when JS is disabled:
<div class=\\"hidden noscript:block\\">Please enable JavaScript to use this app.</div>
You could already do this with the <noscript>
tag but now you can do it with CSS too, which Tailwind lets you write in your HTML, so… yeah.
user-valid
and user-invalid
Ever tried the :invalid
pseudo-class only for the page to be full of red invalid states as soon as it loads, before the user has even touched your form?
The new user-valid
and user-invalid
variants try to solve this problem, by only applying validation-related styling after the user has actually interacted with the controls:
<input required class=\\"border user-valid:border-green-500\\" /><input required class=\\"border user-invalid:border-red-500\\" />
So that\'s it, that\'s Tailwind CSS v4.1! Update to the latest version using npm and start playing with it today:
\\nnpm install tailwindcss@latest @tailwindcss/cli@latest
npm install tailwindcss@latest @tailwindcss/vite@latest
npm install tailwindcss@latest @tailwindcss/postcss@latest
Looking forward to seeing what you build with the new features!
","description":"I wasn\'t sure it would ever happen but we did it — we released a version of Tailwind CSS that includes text-shadow utilities. Tailwind CSS v4.1 is here and it\'s packed with new utilities, variants, and developer experience improvements that will help you (or your LLM, you coward…","guid":"Tailwind CSS v4.1: Text shadows, masks, and tons more","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2025-04-03T20:00:00.441Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.09319a01.jpg","type":"photo","width":3840,"height":2160,"blurhash":"L01{gD?w9E9F%Nt7M|Rk_4%NITIU"},{"url":"https://tailwindcss.com/_next/static/media/keyboard-light.4a0a59a0.png","type":"photo","width":1282,"height":532,"blurhash":"L5L#2+?bRk~q-;-;%L%M_3M{M{kC"},{"url":"https://tailwindcss.com/_next/static/media/keyboard-dark.d0689e65.png","type":"photo","width":1282,"height":532,"blurhash":"L02P0.ogRixwngV@a~adj?a{WAj?"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsafari-15-light.3d71def0.png&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsafari-15-dark.5f5de8bc.png&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://spotlight.tailwindui.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Favatar.51a13c67.jpg&w=128&q=80","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://images.unsplash.com/photo-1590895340509-793cb98788c9?q=80&w=256&h=256&&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Tailwind UI is now Tailwind Plus","url":"https://tailwindcss.com/blog/tailwind-plus","content":"We just shipped a huge rebrand project, turning what was previously known as Tailwind UI into Tailwind Plus.
\\nBefore you jump to any conclusions, here\'s what\'s not changing:
\\nSo why the change? Simply put, there\'s a lot we want to do for our community that doesn\'t feel like it fits into the Tailwind UI box.
\\nHere are some of the ideas we\'ve been exploring for Tailwind Plus:
\\nSome of these ideas are further along than others and some of them might not end up being good ideas at all, but I\'m really excited to keep exploring them in the coming months.
\\nOf course we\'re also going to keep investing into more component examples, templates, and Catalyst improvements! We\'re itching to design a combobox for Catalyst soon for example, and we\'re also about to start prototyping plain HTML versions of our templates for people who don\'t use React.
\\nSo ultimately Tailwind Plus is the all same high-quality resources you know from Tailwind UI, but with all-new possibilities and potential.
\\nIt\'s a big risk to make a change like this, but it\'s something I\'ve wanted to do for years and I\'m excited to finally pull the trigger and ship it.
\\nSo here\'s to a new era — can\'t wait to build you some awesome new stuff.
","description":"We just shipped a huge rebrand project, turning what was previously known as Tailwind UI into Tailwind Plus. Before you jump to any conclusions, here\'s what\'s not changing:\\n\\nNo subscription pricing — it\'s still a one-time purchase with lifetime access, just like it always has…","guid":"Tailwind UI is now Tailwind Plus","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2025-03-04T16:00:00.955Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.000d618e.jpg","type":"photo","width":2400,"height":1260,"blurhash":"LMRfUt.R-=^*~W.7IBRPt,Q.RhXT"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard.000d618e.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Tailwind CSS v4.0","url":"https://tailwindcss.com/blog/tailwindcss-v4","content":"Holy shit it\'s actually done — we just tagged Tailwind CSS v4.0.
\\nTailwind CSS v4.0 is an all-new version of the framework optimized for performance and flexibility, with a reimagined configuration and customization experience, and taking full advantage of the latest advancements the web platform has to offer.
\\n@property
, and color-mix()
.color-scheme
, field-sizing
, complex shadows, inert
, and more.Start using Tailwind CSS v4.0 today by installing it in a new project, or playing with it directly in the browser on Tailwind Play.
\\nFor existing projects, we\'ve published a comprehensive upgrade guide and built an automated upgrade tool to get you on the latest version as quickly and painlessly as possible.
\\nTailwind CSS v4.0 is a ground-up rewrite of the framework, taking everything we\'ve learned about the architecture over the years and optimizing it to be as fast as possible.
\\nWhen benchmarking it on our own projects, we\'ve found full rebuilds to be over 3.5x faster, and incremental builds to be over 8x faster.
\\nHere are the median build times we saw when we benchmarked Tailwind CSS v4.0 against Catalyst:
\\nv3.4 | v4.0 | Improvement | |
---|---|---|---|
Full build | 378ms | 100ms | 3.78x |
Incremental rebuild with new CSS | 44ms | 5ms | 8.8x |
Incremental rebuild with no new CSS | 35ms | 192µs | 182x |
The most impressive improvement is on incremental builds that don\'t actually need to compile any new CSS — these builds are over 100x faster and complete in microseconds. And the longer you work on a project, the more of these builds you run into because you\'re just using classes you\'ve already used before, like flex
, col-span-2
, or font-bold
.
The platform has evolved a lot since we released Tailwind CSS v3.0, and v4.0 takes full advantage of many of these improvements.
\\n@layer theme, base, components, utilities;@layer utilities { .mx-6 { margin-inline: calc(var(--spacing) * 6); } .bg-blue-500\\\\/50 { background-color: color-mix(in oklab, var(--color-blue-500) 50%, transparent); }}@property --tw-gradient-from { syntax: \\"<color>\\"; inherits: false; initial-value: #0000;}
We\'re leveraging modern CSS features like:
\\ncurrentColor
.Many of these features have even simplified Tailwind internally, reducing the surface area for bugs and making the framework easier for us to maintain.
\\nWe\'ve streamlined the setup process a ton in v4.0, reducing the number of steps and removing a lot of boilerplate.
\\nnpm i tailwindcss @tailwindcss/postcss;
export default { plugins: [\\"@tailwindcss/postcss\\"],};
@import \\"tailwindcss\\";
With the improvements we\'ve made to this process for v4.0, Tailwind feels more light-weight than ever:
\\n@tailwind
directives, just add @import \\"tailwindcss\\"
and start building.@import
rules for you out of the box, and use Lightning CSS under the hood for vendor prefixing and modern syntax transforms.Sure you only go through this once per project, but it adds up when you\'re starting and abandoning a different side-project every weekend.
\\nIf you\'re a Vite user, you can now integrate Tailwind using @tailwindcss/vite
instead of PostCSS:
import { defineConfig } from \\"vite\\";import tailwindcss from \\"@tailwindcss/vite\\";export default defineConfig({ plugins: [ tailwindcss(), ],});
Tailwind CSS v4.0 is incredibly fast when used as a PostCSS plugin, but you\'ll get even better performance using the Vite plugin.
\\nYou know how you always had to configure that annoying content
array in Tailwind CSS v3? In v4.0, we came up with a bunch of heuristics for detecting all of that stuff automatically so you don’t have to configure it at all.
For example, we automatically ignore anything in your .gitignore
file to avoid scanning dependencies or generated files that aren’t under version control:
/node_modules/coverage/.next//build
We also automatically ignore all binary extensions like images, videos, .zip files, and more.
\\nAnd if you ever need to explicitly add a source that\'s excluded by default, you can always add it with the @source
directive, right in your CSS file:
@import \\"tailwindcss\\";@source \\"../node_modules/@my-company/ui-lib\\";
The @source
directive uses the same heuristics under the hood, so it will exclude binary file types for example as well, without you having to specify all of the extensions to scan explicitly.
Learn more about in our new documentation on detecting classes in source files.
\\nBefore v4.0, if you wanted to inline other CSS files using @import
you\'d have to configure another plugin like postcss-import
to handle it for you.
Now we handle this out of the box, so you don\'t need any other tools:
\\nexport default { plugins: [ \\"postcss-import\\", \\"@tailwindcss/postcss\\", ],};
Our import system is purpose-built for Tailwind CSS, so we\'ve also been able to make it even faster by tightly integrating it with our engine.
\\nOne of the biggest changes in Tailwind CSS v4.0 is the shift from configuring your project in JavaScript to configuring it in CSS.
\\nInstead of a tailwind.config.js
file, you can configure all of your customizations directly in the CSS file where you import Tailwind, giving you one less file to worry about in your project:
@import \\"tailwindcss\\";@theme { --font-display: \\"Satoshi\\", \\"sans-serif\\"; --breakpoint-3xl: 1920px; --color-avocado-100: oklch(0.99 0 0); --color-avocado-200: oklch(0.98 0.04 113.22); --color-avocado-300: oklch(0.94 0.11 115.03); --color-avocado-400: oklch(0.92 0.19 114.08); --color-avocado-500: oklch(0.84 0.18 117.33); --color-avocado-600: oklch(0.53 0.12 118.34); --ease-fluid: cubic-bezier(0.3, 0, 0, 1); --ease-snappy: cubic-bezier(0.2, 0, 0, 1); /* ... */}
The new CSS-first configuration lets you do just about everything you could do in your tailwind.config.js
file, including configuring your design tokens, defining custom utilities and variants, and more.
To learn more about how it all works, read the new theme variables documentation.
\\nTailwind CSS v4.0 takes all of your design tokens and makes them available as CSS variables by default, so you can reference any value you need at run-time using just CSS.
\\nUsing the example @theme
from earlier, all of these values will be added to your CSS to as regular custom properties:
:root { --font-display: \\"Satoshi\\", \\"sans-serif\\"; --breakpoint-3xl: 1920px; --color-avocado-100: oklch(0.99 0 0); --color-avocado-200: oklch(0.98 0.04 113.22); --color-avocado-300: oklch(0.94 0.11 115.03); --color-avocado-400: oklch(0.92 0.19 114.08); --color-avocado-500: oklch(0.84 0.18 117.33); --color-avocado-600: oklch(0.53 0.12 118.34); --ease-fluid: cubic-bezier(0.3, 0, 0, 1); --ease-snappy: cubic-bezier(0.2, 0, 0, 1); /* ... */}
This makes it easy to reuse these values as inline styles or pass them to libraries like Motion to animate them.
\\nWe\'ve simplified the way many utilities and variants work in v4.0 by effectively allowing them to accept certain types of arbitrary values, without the need for any configuration or dropping down to the arbitrary value syntax.
\\nFor example, in Tailwind CSS v4.0 you can create grids of any size out of the box:
\\n<div class=\\"grid grid-cols-15\\"> <!-- ... --\x3e</div>
You can also target custom boolean data attributes without needing to define them:
\\n<div data-current class=\\"opacity-75 data-current:opacity-100\\"> <!-- ... --\x3e</div>
Even spacing utilities like px-*
, mt-*
, w-*
, h-*
, and more are now dynamically derived from a single spacing scale variable and accept any value out of the box:
@layer theme { :root { --spacing: 0.25rem; }}@layer utilities { .mt-8 { margin-top: calc(var(--spacing) * 8); } .w-17 { width: calc(var(--spacing) * 17); } .pr-29 { padding-right: calc(var(--spacing) * 29); }}
The upgrade tool we released alongside v4.0 will even simplify most of these utilities for you automatically if it notices you using an arbitrary value that\'s no longer needed.
\\nWe\'ve upgraded the entire default color palette from rgb
to oklch
, taking advantage of the wider gamut to make the colors more vivid in places where we were previously limited by the sRGB color space.
We\'ve tried to keep the balance between all the colors the same as it was in v3, so even though we\'ve refreshed things across the board, it shouldn\'t feel like a breaking change when upgrading your existing projects.
\\nWe\'ve brought container query support into core for v4.0, so you don\'t need the @tailwindcss/container-queries
plugin anymore:
<div class=\\"@container\\"> <div class=\\"grid grid-cols-1 @sm:grid-cols-3 @lg:grid-cols-4\\"> <!-- ... --\x3e </div></div>
We\'ve also added support for max-width container queries using the new @max-*
variant:
<div class=\\"@container\\"> <div class=\\"grid grid-cols-3 @max-md:grid-cols-1\\"> <!-- ... --\x3e </div></div>
Like our regular breakpoint variants, you can also stack @min-*
and @max-*
variants to define container query ranges:
<div class=\\"@container\\"> <div class=\\"flex @min-md:@max-xl:hidden\\"> <!-- ... --\x3e </div></div>
Learn more in our all-new container queries documentation.
\\nWe\'ve finally added APIs for doing 3D transforms, like rotate-x-*
, rotate-y-*
, scale-z-*
, translate-z-*
, and tons more.
Boost your conversion rate
<div class=\\"perspective-distant\\"> <article class=\\"rotate-x-51 rotate-z-43 transform-3d ...\\"> <!-- ... --\x3e </article></div>
Check out the updated transform-style
, rotate
, perspective
, and perspective-origin
documentation to get started.
We\'ve added a ton of new gradient features in v4.0, so you can pull off even fancier effects without having to write any custom CSS.
\\nLinear gradients now support angles as values, so you can use utilities like bg-linear-45
to create a gradient on a 45 degree angle:
<div class=\\"bg-linear-45 from-indigo-500 via-purple-500 to-pink-500\\"></div>
You may notice we\'ve renamed bg-gradient-*
to bg-linear-*
too — you\'ll see why shortly!
We\'ve added the ability to control the color interpolation mode for gradients using a modifier, so a class like bg-linear-to-r/srgb
interpolates using sRGB, and bg-linear-to-r/oklch
interpolates using OKLCH:
<div class=\\"bg-linear-to-r/srgb from-indigo-500 to-teal-400\\">...</div><div class=\\"bg-linear-to-r/oklch from-indigo-500 to-teal-400\\">...</div>
Using polar color spaces like OKLCH or HSL can lead to much more vivid gradients when the from-*
and to-*
colors are far apart on the color wheel. We\'re using OKLAB by default in v4.0 but you can always interpolate using a different color space by adding one of these modifiers.
We\'ve added new bg-conic-*
and bg-radial-*
utilities for creating conic and radial gradients:
<div class=\\"size-24 rounded-full bg-conic/[in_hsl_longer_hue] from-red-600 to-red-600\\"></div><div class=\\"size-24 rounded-full bg-radial-[at_25%_25%] from-white to-zinc-900 to-75%\\"></div>
These new utilities work alongside the existing from-*
, via-*
, and to-*
utilities to let you create conic and radial gradients the same way you create linear gradients, and include modifiers for setting the color interpolation method and arbitrary value support for controlling details like the gradient position.
The new starting
variant adds support for the new CSS @starting-style
feature, making it possible to transition element properties when an element is first displayed:
<div> <button popovertarget=\\"my-popover\\">Check for updates</button> <div popover id=\\"my-popover\\" class=\\"transition-discrete starting:open:opacity-0 ...\\"> <!-- ... --\x3e </div></div>
With @starting-style
, you can finally animate elements as they appear on the page without the need for any JavaScript at all. Browser support probably isn\'t quite there yet for most teams, but we\'re getting close!
We\'ve added a new not-*
variant which finally adds support for the CSS :not()
pseudo-class:
<div class=\\"not-hover:opacity-75\\"> <!-- ... --\x3e</div>
.not-hover\\\\:opacity-75:not(*:hover) { opacity: 75%;}@media not (hover: hover) { .not-hover\\\\:opacity-75 { opacity: 75%; }}
It does double duty and also lets you negate media queries and @supports
queries:
<div class=\\"not-supports-hanging-punctuation:px-4\\"> <!-- ... --\x3e</div>
.not-supports-hanging-punctuation\\\\:px-4 { @supports not (hanging-punctuation: var(--tw)) { padding-inline: calc(var(--spacing) * 4); }}
Check out the new not-*
documentation to learn more.
We\'ve added a ton of other new utilities and variants to v4.0 too, including:
\\ninset-shadow-*
and inset-ring-*
utilities — making it possible to stack up to four layers of box shadows on a single element.field-sizing
utilities — for auto-resizing textareas without writing a single line of JavaScript.color-scheme
utilities — so you can finally get rid of those ugly light scrollbars in dark mode.font-stretch
utilities — for carefully tweaking variable fonts that support different widths.inert
variant — for styling non-interactive elements marked with the inert
attribute.nth-*
variants — for doing really clever things you\'ll eventually regret.in-*
variant — which is a lot like group-*
, but without the need for the group
class.:popover-open
— using the existing open
variant to also target open popovers.Check out the relevant documentation for all of these features to learn more.
\\nAnd that\'s it — that\'s Tailwind CSS v4.0. It\'s been years of work to get to this point, but we\'re all extremely proud of this release and we can\'t wait to see what you build with it.
\\nCheck it out, play with it, maybe even break it, and definitely let us know what you think.
\\nJust no bug reports until tomorrow please — let us at least enjoy one celebratory team dinner and maybe relax in the hot tub at this hotel for a bit believing that somehow we really did ship flawless software.
","description":"Holy shit it\'s actually done — we just tagged Tailwind CSS v4.0. Tailwind CSS v4.0 is an all-new version of the framework optimized for performance and flexibility, with a reimagined configuration and customization experience, and taking full advantage of the latest advancements…","guid":"Tailwind CSS v4.0","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2025-01-22T22:00:00.140Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.22502194.jpg","type":"photo","width":1200,"height":630,"blurhash":"L22$X4NGM{of4Tt6xuay.9R*Rjj["},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard.22502194.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://images.unsplash.com/photo-1496128858413-b36217c2ce36?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3603&q=80","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Tailwind CSS v4.0 Beta 1","url":"https://tailwindcss.com/blog/tailwindcss-v4-beta","content":"About eight months ago we open-sourced our progress on Tailwind CSS v4.0. Hundreds of hours of fixing bugs, soul-crushing backward compatibility work, and troubleshooting Windows CI failures later, I\'m excited to finally tag the first public beta release.
\\nAs I talked about when we published the first alpha, Tailwind CSS v4.0 is an all-new engine built for performance, and designed for the modern web.
\\n@starting-style
, popovers, and more.There\'s so much more to say, but everything you need to get started is in the new beta documentation we published today:
\\nGet started with Tailwind CSS v4.0 Beta 1 →
\\nStart building and help us bullet-proof this thing for the stable release early in the new year.
","description":"About eight months ago we open-sourced our progress on Tailwind CSS v4.0. Hundreds of hours of fixing bugs, soul-crushing backward compatibility work, and troubleshooting Windows CI failures later, I\'m excited to finally tag the first public beta release. As I talked about when…","guid":"Tailwind CSS v4.0 Beta 1","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2024-11-21T18:30:00.309Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.d69e3d8c.jpg","type":"photo","width":3840,"height":2160,"blurhash":"L12rs-tSD*WXozflWVa}4TRP-:od"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard.d69e3d8c.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Radiant: A beautiful new marketing site template","url":"https://tailwindcss.com/blog/2024-09-12-radiant-a-beautiful-new-marketing-site-template","content":"We just wrapped up work on a beautiful new SaaS marketing site template called Radiant, and it\'s available now as part of Tailwind UI.
\\nIt’s built with Next.js, Framer Motion, and Tailwind CSS, with a blog powered by Sanity.
\\nIt\'s been a while since we built a SaaS marketing template like this, and in that time we\'ve learned a lot about what makes a template like this useful and easy to work with. We\'ve tried to incorporate all of those learnings into Radiant.
\\nCheck out the live preview as always for the full experience — there are tons of cool details in this one that you have to see in the browser to really appreciate.
\\nIt\'s super easy to overdo animation on a site like this. We\'ve all seen sites where you can\'t even scroll a few pixels without a bunch of different elements animating in to place. Even worse is how slow things feel when you have to wait for the content to appear before you can read it.
\\nRadiant is loaded with delightful animations, but they are all layered on to existing content and triggered by user interaction so the site still feels fast. In most cases, we went for animations that loop to make elements feel \\"alive\\" while you\'re interacting with them.
\\nWe used Framer Motion for almost all of the animations. It\'s declarative, making it easy to create our own APIs for complex animations that other people can customize without much effort.
\\nIt does have some drawbacks to work around though. For example, when you have multiple elements animating independently it\'s annoying to pass a hover state down to each child. We ended up leveraging Framer\'s variant propagation to make this work — a hover event triggers a variant change in the parent that propagates down to the children because they share the same variant keys.
\\nexport function BentoCard() { return ( <motion.div initial=\\"idle\\" whileHover=\\"active\\" variants={{ idle: {}, active: {} }} data-dark={dark ? \\"true\\" : undefined} > /* ... */ </motion.div> );}
There is no difference between the variants in the parent so it doesn\'t actually change but the children still get a signal to change variants on hover, even if they are deeply nested.
\\nfunction Marker({ src, top, offset, delay,}: { src: string top: number offset: number delay: number}) { return ( <motion.div variants={{ idle: { scale: 0, opacity: 0, rotateX: 0, rotate: 0, y: 0 }, active: { y: [-20, 0, 4, 0], scale: [0.75, 1], opacity: [0, 1] }, }} transition={{ duration: 0.25, delay, ease: \'easeOut\' }} style={{ \'--offset\': `${offset}px`, top } as React.CSSProperties} className=\\"absolute left-[calc(50%+var(--offset))] size-[38px] drop-shadow-[0_3px_1px_rgba(0,0,0,.15)]\\" > /* ... */ </motion.div> )}/* ... */
The logo timeline animation is a bit different, because we wanted the logos to pause in their current position when you stop hovering, rather than return to their original position. This doesn\'t play very well with Framer\'s approach of specifying start and end states, so it was actually easier to build this in CSS.
\\nIt exploits the fact that you can set a negative animation-delay
value to offset the start position of the element. That way all the logos share the same animation keyframes but they can start at different positions and have different durations.
function Logo({ label, src, className,}: { label: string src: string className: string}) { return ( <div className={clsx( className, \'absolute top-2 grid grid-cols-[1rem,1fr] items-center gap-2 whitespace-nowrap px-3 py-1\', \'rounded-full bg-gradient-to-t from-gray-800 from-50% to-gray-700 ring-1 ring-inset ring-white/10\', \'[--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] [animation-iteration-count:infinite] [animation-name:move-x] [animation-play-state:paused] [animation-timing-function:linear] group-hover:[animation-play-state:running]\', )} > <img alt=\\"\\" src={src} className=\\"size-4\\" /> <span className=\\"text-sm/6 font-medium text-white\\">{label}</span> </div> )}export function LogoTimeline() { return ( /* ... */ <Row> <Logo label=\\"Loom\\" src=\\"./logo-timeline/loom.svg\\" className=\\"[animation-delay:-26s] [animation-duration:30s]\\" /> <Logo label=\\"Gmail\\" src=\\"./logo-timeline/gmail.svg\\" className=\\"[animation-delay:-8s] [animation-duration:30s]\\" /> </Row> /* ... */
This approach means we don\'t need to track the play state in JavaScript, we can just use a group-hover:[animation-play-state:running]
class to start the animation when the parent is hovered.
As you\'ve maybe noticed, we\'re using a bunch of arbitrary properties for individual animation
properties in this component, since these utilities don\'t exist in Tailwind today. This is what\'s great about building these templates — it helps us find blind spots in Tailwind CSS. Who knows, maybe we\'ll see these utilities added for v4.0!
The trickiest part of designing a SaaS template like this, is coming up with interactive elements that people can apply to their own product without too much effort. There\'s nothing worse than buying a template and realizing that it\'s so specific to the example content that you can\'t actually use it for your own project.
\\nWe came up with some core graphical elements that most SaaS products might have. A map with pins, a logo cluster, a keyboard — things that could be applied to a bunch of different features. Because we wanted them to be easy to repurpose for your own product, we built a lot of them in code and designed nice APIs for them.
\\nThe logo cluster, for example, has a simple API that lets you pass in your own logos, tweak their position and hover animation to match.
\\n<Logo src=\\"./logo-cluster/dribbble.svg\\" left={285} top={20} hover={{ x: 4, y: -5, rotate: 6, delay: 0.3 }} />
The keyboard shortcuts section is another good example. Adding your own shortcuts is as simple as passing an array of key names to the Keyboard component and because each key is a component, you can easily add custom keys or change the layout.
\\n<Keyboard highlighted={[\\"F\\", \\"M\\", \\"L\\"]} />
It turns out it\'s actually quite a lot of work to build a keyboard in code, but at least now you\'ll never have to find that out for yourself.
\\nOf course, we also left spots for you to drop in screenshots of your own product. Here\'s what this section looks like customized to suit our friends at SavvyCal, using the same interactive components.
\\nUsually we just use MDX when adding a blog to a template, but this time we thought it would be fun to play with a headless CMS for a chance instead. We decided to give Sanity a go for this one after polling our audience and hearing a lot of good things.
\\nInstead of creating files, making commits, and managing images and stuff by hand, a CMS lets you handle everything from their UI, so even non-developers can easily contribute.
\\nOne cool thing about headless CMSes like Sanity is you get your content back in a structured format, so similar to MDX you can map elements to your own custom components to handle all of your typography styles.
\\n<PortableText value={post.body} components={{ block: { normal: ({ children }) => <p className=\\"my-10 text-base/8 first:mt-0 last:mb-0\\">{children}</p>, h2: ({ children }) => ( <h2 className=\\"mt-12 mb-10 text-2xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0\\"> {children} </h2> ), h3: ({ children }) => ( <h3 className=\\"mt-12 mb-10 text-xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0\\"> {children} </h3> ), blockquote: ({ children }) => ( <blockquote className=\\"my-10 border-l-2 border-l-gray-300 pl-6 text-base/8 text-gray-950 first:mt-0 last:mb-0\\"> {children} </blockquote> ), }, types: { image: ({ value }) => ( <img className=\\"w-full rounded-2xl\\" src={image(value).width(2000).url()} alt={value.alt || \\"\\"} /> ), }, /* ... */ }}/>
Working with a CMS also means all of your assets like images are hosted for you, and you can control the size, quality, and format of the image on the fly.
\\n\\n<div className=\\"text-sm/5 max-sm:text-gray-700 sm:font-medium\\"> {dayjs(post.publishedAt).format(\'dddd, MMMM D, YYYY\')}</div>{post.author && ( <div className=\\"mt-2.5 flex items-center gap-3\\"> {post.author.image && ( <img className=\\"aspect-square size-6 rounded-full object-cover\\" src={image(post.author.image).width(64).height(64).url()} alt=\\"\\" /> )} <div className=\\"text-sm/5 text-gray-700\\"> {post.author.name} </div> </div>)}
Like you might do with front matter in Markdown, you can also enrich content with custom fields. For example, we added a featured
boolean field to the blog post schema so you can highlight some posts in a special section on the blog.
Sanity in particular is a paid product, but they have a pretty generous free tier which is more than enough to play around. And if you wanted to try out a different headless CMS, I think the Sanity integration we\'ve put together here will still serve as a great informative example of how you might approach wiring things up with another tool.
\\nAnd that\'s Radiant! Have a look under the hood, kick the tires, and let us know what you think.
\\nLike all of our templates, it\'s included with a one-time purchase Tailwind UI all-access license, which is the best way to support our work on Tailwind CSS and make it possible for us to keep building awesome stuff for you for years to come.
","description":"We just wrapped up work on a beautiful new SaaS marketing site template called Radiant, and it\'s available now as part of Tailwind UI. It’s built with Next.js, Framer Motion, and Tailwind CSS, with a blog powered by Sanity.\\n\\nIt\'s been a while since we built a SaaS marketing…","guid":"Radiant: A beautiful new marketing site template","author":"Dan Hollick","authorUrl":"","authorAvatar":null,"publishedAt":"2024-09-12T10:30:00.466Z","media":[{"url":"https://tailwindcss.com/_next/static/media/twitter-card.28f42ad3.png","type":"photo","width":1280,"height":720,"blurhash":"LWO{XLF.4:D*9poNsXofGYZ-jYax"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftemplate.4912d398.png&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://assets.tailwindcss.com/blog/2024-09-12-radiant-a-beautiful-new-marketing-site-template/scheduling-animation.mp4","type":"video"},{"url":"https://assets.tailwindcss.com/blog/2024-09-12-radiant-a-beautiful-new-marketing-site-template/logo-timeline.mp4","type":"video"},{"url":"https://assets.tailwindcss.com/blog/2024-09-12-radiant-a-beautiful-new-marketing-site-template/light-bento.mp4","type":"video"},{"url":"https://assets.tailwindcss.com/blog/2024-09-12-radiant-a-beautiful-new-marketing-site-template/keyboard.mp4","type":"video"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsavvycal-radiant.8556dfac.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsanity-studio.abf6eac7.png&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fblog.76d3c00e.png&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Headless UI v2.1: Simplified transition API and improved multi-dialog support","url":"https://tailwindcss.com/blog/2024-06-21-headless-ui-v2-1","content":"We just released Headless UI v2.1 for React, which dramatically simplifies our transition APIs and adds support for rendering multiple dialogs as siblings.
\\nWe\'ve made transitions way easier in v2.1 by adding a new transition
prop to all of the built-in components you might want to transition, and adding data attributes for each transition stage so you can add transition styles by just throwing some classes on the target element:
import { Menu, MenuButton, MenuItem, MenuItems } from \\"@headlessui/react\\";function Example() { return ( <Menu> <MenuButton>My account</MenuButton> <MenuItems transition className={` transition ease-out data-[closed]:scale-95 data-[closed]:opacity-0 data-[enter]:duration-200 data-[leave]:duration-300 `} > {/* Menu items… */} </MenuItems> </Menu> );}
There are four data attributes you can use to target the different stages of your transitions:
\\ndata-closed
: The styles the element should transition from when entering and to when leaving.data-enter
: Styles to apply while the element is entering, like a duration or easing curve.data-leave
: Styles to apply while the element is leaving, like a duration or easing curve.data-transition
: Styles to apply while the element is entering or leaving, useful for sharing values between both stages.You can even stack these attributes to use different closed
styles for entering and leaving. For example this dialog slides in from the left, but slides out to the right:
import { Dialog } from \\"@headlessui/react\\";import { useState } from \\"react\\";function Example() { let [isOpen, setIsOpen] = useState(false); return ( <> <button onClick={() => setIsOpen(true)}>Open dialog</button> <Dialog open={isOpen} onClose={() => setIsOpen(false)} transition className={` transition duration-300 ease-out data-[closed]:opacity-0 data-[closed]:data-[enter]:-translate-x-8 data-[closed]:data-[leave]:translate-x-8 `} > {/* Dialog content… */} </Dialog> </> );}
And for transitioning regular HTML elements or other components, you can still use the <Transition>
component but with the new data attribute APIs:
import { Transition } from \\"@headlessui/react\\";import { useState } from \\"react\\";function Example() { const [isShowing, setIsShowing] = useState(false); return ( <> <button onClick={() => setIsShowing((isShowing) => !isShowing)}>Toggle</button> <Transition show={isShowing}> <div className=\\"transition duration-300 data-[closed]:opacity-0\\">I will fade in and out</div> </Transition> </> );}
We\'ve already updated all of Tailwind UI to use this new transition API and the code is a lot simpler and lighter. Take a look at the Modal Dialog, Dropdown, Slide-over, Flyout Menu, or Select Menu components for more examples.
\\nAll of the existing APIs continue to work for backwards compatibility, but this new approach is what we\'re going to recommend going forward.
\\nCheck out the updated Transition
component documentation to learn more.
In Headless UI v2.1 you can finally render multiple dialogs at the same time without nesting one inside the other.
\\nThis can be really helpful when two unrelated parts of your application need to show a dialog at the same time — for example maybe you already have some sort of confirmation dialog open but another part of your app detects that you\'ve lost network connectivity or your session has timed-out and needs to throw up a new dialog on top.
\\nHere\'s what something like that might look like with Catalyst, the application UI kit we\'ve been working on recently:
\\nWe keep track of the order in which each dialog is opened, and whichever one was opened last is the one that will close when you press escape or click outside the dialog.
\\nTo start using this stuff today, just install the latest version of Headless UI:
\\nnpm i @headlessui/react@latest
If you run into any issues, let us know on GitHub!
","description":"We just released Headless UI v2.1 for React, which dramatically simplifies our transition APIs and adds support for rendering multiple dialogs as siblings. Simplified transition API\\n\\nWe\'ve made transitions way easier in v2.1 by adding a new transition prop to all of the built-in…","guid":"Headless UI v2.1: Simplified transition API and improved multi-dialog support","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2024-06-21T15:00:00.494Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.647cc6f3.jpg","type":"photo","width":1536,"height":768,"blurhash":"LK4V35Rds=kDtWRfoNbbo$ROawkD"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard.647cc6f3.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://assets.tailwindcss.com/blog%2F2024-06-21-headless-ui-v2-1%2Fheadlessui-sibling-dialogs.mp4","type":"video"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Automatically clean up whitespace and duplicate class names","url":"https://tailwindcss.com/blog/2024-05-30-prettier-plugin-collapse-whitespace","content":"We just released a new version of prettier-plugin-tailwindcss
which adds support for removing unnecessary whitespace and duplicate classes when sorting.
When you\'re copying around class names or deleting a class name from the middle of a list, it\'s easy to end up with some extra whitespace that needs to be cleaned up.
\\nNow our Prettier plugin will handle this for you automatically, so you don\'t need to clean it up yourself.
\\nOur VS Code extension has warned you about duplicate class names for a long time, but now our Prettier plugin can remove those duplicate classes for you automatically.
\\nTo start playing with these improvements in your own projects, just install the latest version:
\\nnpm i prettier-plugin-tailwindcss@latest
If you run into any issues, let us know on GitHub!
","description":"We just released a new version of prettier-plugin-tailwindcss which adds support for removing unnecessary whitespace and duplicate classes when sorting. Cleaning up unnecessary whitespace\\n\\nWhen you\'re copying around class names or deleting a class name from the middle of a list,…","guid":"Automatically clean up whitespace and duplicate class names","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2024-05-31T16:30:00.516Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.2d498a85.jpg","type":"photo","width":1536,"height":736,"blurhash":"L43+=-bqZ}tSyGk?kCn$H?g5gNRj"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard.2d498a85.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://assets.tailwindcss.com/blog/2024-05-30-prettier-plugin-collapse-whitespace/collapse-whitespace.mp4","type":"video"},{"url":"https://assets.tailwindcss.com/blog/2024-05-30-prettier-plugin-collapse-whitespace/remove-duplicates.mp4","type":"video"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Catalyst: Application layouts, navigation menus, description lists, and more","url":"https://tailwindcss.com/blog/2024-05-24-catalyst-application-layouts","content":"We just published the first major update to Catalyst since releasing the development preview, with two new application layouts, navbar and sidebar components, description lists, and more.
\\nWe\'re also pumped to share that with the release of Headless UI v2.0 for React, Catalyst is no longer in development preview — it\'s officially stable and you can start using it in production today without worrying about breaking changes in the underlying dependencies.
\\nCheck out our brand new live demo site to see what a full Catalyst project looks and feels like after these updates for yourself.
\\nOne of the hardest things about trying to get started on a new project idea is getting past the blank canvas so you can actually start building something.
\\nIn this update we\'ve added two new application layout components to make it easy to give your project a shape and structure so you have something you can start building with.
\\nThe first layout is a classic sidebar layout, that moves the sidebar into a collapsible mobile menu on smaller screens:
\\nimport { SidebarLayout } from \\"@/components/sidebar-layout\\";import { Navbar } from \\"@/components/navbar\\";import { Sidebar } from \\"@/components/sidebar\\";function Example({ children }) { return ( <SidebarLayout sidebar={<Sidebar>{/* Sidebar menu */}</Sidebar>} navbar={<Navbar>{/* Navbar for mobile screens */}</Navbar>} > {/* Your page content */} </SidebarLayout> );}
The second is a simpler stacked layout with a horizontal navigation menu, which is often a great fit for apps with fewer pages:
\\nimport { StackedLayout } from \\"@/components/stacked-layout\\";import { Navbar } from \\"@/components/navbar\\";import { Sidebar } from \\"@/components/sidebar\\";function Example({ children }) { return ( <StackedLayout navbar={<Navbar>{/* Top navigation menu */}</Navbar>} sidebar={<Sidebar>{/* Sidebar content for mobile menu */}</Sidebar>} > {/* Your page content */} </StackedLayout> );}
And they both support dark mode too, of course:
\\nWe worked really hard to get the APIs for all of these components right, making it easy to position things where you need them to be, optionally include icons, incorporate dropdown menus, and more.
\\nThe final result turned out feeling really simple which is exactly what we were going for, and I think you\'ll find they are a real delight to build with.
\\nCheck out the Sidebar layout documentation and Stacked layout documentation to get started, then dig into the Navbar and Sidebar components to learn how to structure all of the navigation items.
\\nWhen we were working on the application layouts we realized we didn\'t have any great content to demo them with, so we cooked up a DescriptionList
component to fill in that big empty space.
import { DescriptionDetails, DescriptionList, DescriptionTerm } from \\"@/components/description-list\\";function Example() { return ( <DescriptionList> <DescriptionTerm>Customer</DescriptionTerm> <DescriptionDetails>Michael Foster</DescriptionDetails> <DescriptionTerm>Event</DescriptionTerm> <DescriptionDetails>Bear Hug: Live in Concert</DescriptionDetails> {/* ... */} </DescriptionList> );}
It\'s a really simple API that works just like the HTML <dl>
element, but is nicely styled, responsive, and with dark mode support of course.
Check out the Description list documentation for more details.
\\nMore components we needed to make the demo look good! We\'ve added Heading
and Subheading
components you can use to quickly and consistently title things in your UI.
import { Heading, Subheading } from \\"@/components/heading\\";function Example() { return ( <div> <Heading>Heading</Heading> <Subheading>Subheading</Subheading> </div> );}
You can control which HTML heading element is rendered using the level
prop, and like everything else, they\'re responsive with built-in dark mode support.
See the Heading documentation for more examples.
\\nSaved the best for last — Catalyst now includes a gray line you can put in between things.
\\nimport { Divider } from \\"@/components/divider\\";function Example() { return <Divider />;}
We worked tirelessly on this one, and are so proud to make this part of your application development process easier.
\\nCheck out the Divider documentation — it does have one prop at least.
\\nCatalyst is included with your Tailwind UI all-access license at no additional cost, so if you\'ve got a license, log in and download the latest version to start building.
\\nLooking forward to seeing what you do with it!
","description":"We just published the first major update to Catalyst since releasing the development preview, with two new application layouts, navbar and sidebar components, description lists, and more. We\'re also pumped to share that with the release of Headless UI v2.0 for React, Catalyst is…","guid":"Catalyst: Application layouts, navigation menus, description lists, and more","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2024-05-24T20:00:00.056Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.325e0321.jpg","type":"photo","width":1600,"height":960,"blurhash":"LfPjGc?bD%-;00RjofWB00M{ofay"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcatalyst-header.5da8820a.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsidebar-layout.e72f12e0.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fstacked-layout.ec57a472.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flayout-dark-mode.29f2de5c.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Headless UI v2.0 for React","url":"https://tailwindcss.com/blog/headless-ui-v2","content":"Nothing beats actually building something real with your own tools when it comes to finding ways to make things better.
\\nAs we\'ve been working on Catalyst these last several months, we\'ve been making dozens of improvements to Headless UI that let you write even less code, and make the developer experience even better.
\\nWe just released Headless UI v2.0 for React, which is the culmination of all this work.
\\nHere\'s all the best new stuff:
\\nAdd it to your project by installing the latest version of @headlessui/react
from npm:
npm install @headlessui/react@latest
If you\'re upgrading from v1.x, check out the upgrade guide to learn more about what\'s changed.
\\nWe\'ve integrated Floating UI directly into Headless UI, so you never have to worry about dropdowns going out of view or being obscured by other elements on the screen.
\\nUse the new anchor
prop on the Menu
, Popover
, Combobox
, and Listbox
components to specify the anchor positioning, then fine-tune the placement with CSS variables like --anchor-gap
and --anchor-padding
:
Scroll up and down to see the dropdown position change
import { Menu, MenuButton, MenuItem, MenuItems } from \\"@headlessui/react\\";function Example() { return ( <Menu> <MenuButton>Options</MenuButton> <MenuItems anchor=\\"bottom start\\" className=\\"[--anchor-gap:8px] [--anchor-padding:8px]\\" > <MenuItem> <button>Edit</button> </MenuItem> <MenuItem> <button>Duplicate</button> </MenuItem> <hr /> <MenuItem> <button>Archive</button> </MenuItem> <MenuItem> <button>Delete</button> </MenuItem> </MenuItems> </Menu> );}
What makes this API really nice is that you can tweak the styles at different breakpoints by changing the CSS variables using utility classes like sm:[--anchor-gap:4px]
.
Check out the anchor positioning documentation for each component for all of the details.
\\nWe\'ve added a new headless Checkbox
component to complement our existing RadioGroup
component, making it easy to build totally custom checkbox controls:
This will give you early access to any awesome new features we\'re developing.
import { Checkbox, Description, Field, Label } from \\"@headlessui/react\\";import { CheckmarkIcon } from \\"./icons/checkmark\\";import clsx from \\"clsx\\";function Example() { return ( <Field> <Checkbox defaultChecked className={clsx( \\"size-4 rounded border bg-white dark:bg-white/5\\", \\"data-[checked]:border-transparent data-[checked]:bg-blue-500\\", \\"focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500\\", )} > <CheckmarkIcon className=\\"stroke-white opacity-0 group-data-[checked]:opacity-100\\" /> </Checkbox> <div> <Label>Enable beta features</Label> <Description>This will give you early access to any awesome new features we\'re developing.</Description> </div> </Field> );}
Checkboxes can be controlled or uncontrolled, and can automatically sync their state with a hidden input to play nicely with HTML forms.
\\nTake a look at the Checkbox documentation to learn more.
\\nWe\'ve added a whole new set of components that just wrap native form controls, but do all of the tedious work of wiring up IDs and aria-*
attributes for you automatically.
Here\'s what it looked like to build a simple <input>
field with a properly associated <label>
and description before:
<div> <label id=\\"name-label\\" for=\\"name-input\\"> Name </label> <input id=\\"name-input\\" aria-labelledby=\\"name-label\\" aria-describedby=\\"name-description\\" /> <p id=\\"name-description\\">Use your real name so people will recognize you.</p></div>
And here\'s what it looks like with these new components in Headless UI v2.0:
\\nimport { Description, Field, Input, Label } from \\"@headlessui/react\\";function Example() { return ( <Field> <Label>Name</Label> <Input name=\\"your_name\\" /> <Description>Use your real name so people will recognize you.</Description> </Field> );}
The new Field
and Fieldset
components also cascade disabled states like the native <fieldset>
element, so you can easily disable an entire group of controls at once:
Select a country to see the region field become enabled
We currently only ship to North America.
import { Button, Description, Field, Fieldset, Input, Label, Legend, Select } from \\"@headlessui/react\\";import { regions } from \\"./countries\\";export function Example() { const [country, setCountry] = useState(null); return ( <form action=\\"/shipping\\"> <Fieldset> <Legend>Shipping details</Legend> <Field> <Label>Street address</Label> <Input name=\\"address\\" /> </Field> <Field> <Label>Country</Label> <Description>We currently only ship to North America.</Description> <Select name=\\"country\\" value={country} onChange={(event) => setCountry(event.target.value)}> <option></option> <option>Canada</option> <option>Mexico</option> <option>United States</option> </Select> </Field> <Field disabled={!country}> <Label className=\\"data-[disabled]:opacity-40\\">State/province</Label> <Select name=\\"region\\" className=\\"data-[disabled]:opacity-50\\"> <option></option> {country && regions[country].map((region) => <option>{region}</option>)} </Select> </Field> <Button>Submit</Button> </Fieldset> </form> );}
We expose the disabled state using a data-disabled
attribute in the rendered HTML. This lets us expose it even on elements that don\'t support the native disabled
attribute like the associated <label>
element, making it really easy to fine-tune the disabled styles for each element.
All in all we\'ve added 8 new components here — Fieldset
, Legend
, Field
, Label
, Description
, Input
, Select
, and Textarea
.
For more details, start with the Fieldset documentation and work your way through the rest.
\\nUsing hooks from the awesome React Aria library under the hood, Headless UI now adds smarter data-*
state attributes to your controls that behave more consistently across different devices than the native CSS pseudo-classes:
data-active
— like :active
, but is removed when dragging off of the element.data-hover
— like :hover
, but is ignored on touch devices to avoid sticky hover states.data-focus
— like :focus-visible
, without false positives from imperative focusing.Click, hover, focus, and drag the button to see the data attributes applied
<!-- Rendered `Button` --\x3e<button class=\\"bg-indigo-600 data-[active]:bg-indigo-700 data-[focus]:outline data-[hover]:bg-indigo-500 ...\\"> Submit</button>
<!-- Rendered `Button` --\x3e<button data-hover class=\\"bg-indigo-600 data-[active]:bg-indigo-700 data-[focus]:outline data-[hover]:bg-indigo-500 ...\\"> Submit</button>
<!-- Rendered `Button` --\x3e<button data-focus class=\\"bg-indigo-600 data-[active]:bg-indigo-700 data-[focus]:outline data-[hover]:bg-indigo-500 ...\\"> Submit</button>
<!-- Rendered `Button` --\x3e<button data-active class=\\"bg-indigo-600 data-[active]:bg-indigo-700 data-[focus]:outline data-[hover]:bg-indigo-500 ...\\"> Submit</button>
<!-- Rendered `Button` --\x3e<button data-focus data-hover class=\\"bg-indigo-600 data-[active]:bg-indigo-700 data-[focus]:outline data-[hover]:bg-indigo-500 ...\\"> Submit</button>
<!-- Rendered `Button` --\x3e<button data-active data-hover class=\\"bg-indigo-600 data-[active]:bg-indigo-700 data-[focus]:outline data-[hover]:bg-indigo-500 ...\\"> Submit</button>
To learn more about why applying these styles using JavaScript is important, I highly recommend reading through Devon Govett\'s excellent blog series on this topic:
\\nThe web never ceases to surprise me with the amount of effort it takes to actually make nice things.
\\nWe\'ve integrated TanStack Virtual into Headless UI to support list virtualization when you need to put a hundred thousand items in your combobox because, hey, that\'s what the boss told you to do.
\\nUse the new virtual
prop to pass in all of your items, and use the ComboboxOptions
render prop to provide the template for an individual option:
Open the combobox and scroll through the 1,000 options
import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions } from \\"@headlessui/react\\";import { ChevronDownIcon } from \\"@heroicons/react/20/solid\\";import { useState } from \\"react\\";const people = [ { id: 1, name: \\"Rossie Abernathy\\" }, { id: 2, name: \\"Juana Abshire\\" }, { id: 3, name: \\"Leonel Abshire\\" }, { id: 4, name: \\"Llewellyn Abshire\\" }, { id: 5, name: \\"Ramon Abshire\\" }, // ...up to 1000 people];function Example() { const [query, setQuery] = useState(\\"\\"); const [selected, setSelected] = useState(people[0]); const filteredPeople = query === \\"\\" ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()); }); return ( <Combobox value={selected} virtual={{ options: filteredPeople }} onChange={(value) => setSelected(value)} onClose={() => setQuery(\\"\\")} > <div> <ComboboxInput displayValue={(person) => person?.name} onChange={(event) => setQuery(event.target.value)} /> <ComboboxButton> <ChevronDownIcon /> </ComboboxButton> </div> <ComboboxOptions> {({ option: person }) => ( <ComboboxOption key={person.id} value={person}> {person.name} </ComboboxOption> )} </ComboboxOptions> </Combobox> );}
Check out the new virtual scrolling documentation to learn more.
\\nTo go along with this major release, we\'ve also significantly revamped the documentation and given the website a fresh coat of paint:
\\nHead over to the new headlessui.com to check it out!
","description":"Nothing beats actually building something real with your own tools when it comes to finding ways to make things better. As we\'ve been working on Catalyst these last several months, we\'ve been making dozens of improvements to Headless UI that let you write even less code, and…","guid":"Headless UI v2.0 for React","author":"Adam Wathan","authorUrl":"","authorAvatar":null,"publishedAt":"2024-05-07T16:00:00.715Z","media":[{"url":"https://tailwindcss.com/_next/static/media/card.a8310012.jpg","type":"photo","width":2000,"height":1000,"blurhash":"Lc5=-jMatmV?t,R4tSV[tPRQoyWY"},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcard.a8310012.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://tailwindcss.com/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fnew-website.c20be03f.jpg&w=3840&q=75","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null}],"readCount":1703,"subscriptionCount":3,"analytics":{"feedId":"147822761462156288","updatesPerWeek":0,"subscriptionCount":3,"latestEntryPublishedAt":null,"view":0}}')