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.
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 we published the first alpha, Tailwind CSS v4.0 is an all-new engine built for performance, and designed for the modern web.
Built for performance — full builds in the new engine are up to 5x faster, and incremental builds are over 100x faster — and measured in microseconds.
Unified toolchain — built-in import handling, vendor prefixing, and syntax transforms, with no additional tooling required.
CSS-first configuration — a reimagined developer experience where you customize and extend the framework directly in CSS instead of a JavaScript configuration file.
Designed for the modern web — built on native cascade layers, wide-gamut colors, and including first-class support for modern CSS features like container queries, @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:
Over the past couple of months we’ve been working away at a new SaaS marketing site template for Tailwind UI. It’s called Radiant, and you can start using it today.
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.
It’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.
Check 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.
It’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.
Radiant 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.
We 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.
It 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.
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.
map.tsx
functionMarker({ src, top, offset, delay,}:{src: string
top: number
offset: number
delay: number
}){return(<motion.divvariants={{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 }asReact.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.
It 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.
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.
We 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.
The logo cluster, for example, has a simple API that lets you pass in your own logos, tweak their position and hover animation to match.
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.
<Keyboardhighlighted={['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.
Of 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.
Usually 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.
Instead 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.
One 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.
<PortableText
value={post.body}
components={{block:{normal:({ children })=>(<pclassName="my-10 text-base/8 first:mt-0 last:mb-0">{children}</p>),h2:({ children })=>(<h2className="mb-10 mt-12 text-2xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">{children}</h2>),h3:({ children })=>(<h3className="mb-10 mt-12 text-xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">{children}</h3>),blockquote:({ children })=>(<blockquoteclassName="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 })=>(<imgclassName="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.
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.
And that’s Radiant! Have a look under the hood, kick the tires, and let us know what you think.
Like 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.
We just released Headless UI v2.1 for React, which dramatically simplifies our transition APIs and adds support for rendering multiple dialogs as siblings.
We just released Headless UI v2.1 for React, which dramatically simplifies our transition APIs and adds support for rendering multiple dialogs as siblings.
We’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:
There are four data attributes you can use to target the different stages of your transitions:
data-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:
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'functionExample(){const[isShowing, setIsShowing]=useState(false)return(<><buttononClick={()=>setIsShowing((isShowing)=>!isShowing)}>Toggle</button><Transitionshow={isShowing}><divclassName="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.
All of the existing APIs continue to work for backwards compatibility, but this new approach is what we’re going to recommend going forward.
In Headless UI v2.1 you can finally render multiple dialogs at the same time without nesting one inside the other.
This 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.
Here’s what something like that might look like with Catalyst, the application UI kit we’ve been working on recently:
We 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.
To start using this stuff today, just install the latest version of Headless UI:
$ npm i @headlessui/react@latest
If you run into any issues, let us know on GitHub!
We just released a new version of prettier-plugin-tailwindcss which adds support for removing unnecessary whitespace and duplicate classes when sorting.
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.
Now our Prettier plugin will handle this for you automatically, so you don’t need to clean it up yourself.
functionMyComponent({ children }){return(<divclassName="mx-auto max-w-7xl px-6 lg:px-8">{children}</div>)}
Our 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.
functionMyComponent({ children }){return(<divclassName="flex bg-zinc-100bg-zinc-100px-4">{children}</div>)}
To start playing with these improvements in your own projects, just install the latest version:
$ npm i prettier-plugin-tailwindcss@latest
If you run into any issues, let us know on GitHub!
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 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 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.
Check out our brand new live demo site to see what a full Catalyst project looks and feels like after these updates for yourself.
One 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.
In 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.
The first layout is a classic sidebar layout, that moves the sidebar into a collapsible mobile menu on smaller screens:
import{SidebarLayout}from'@/components/sidebar-layout'import{Navbar}from'@/components/navbar'import{Sidebar}from'@/components/sidebar'functionExample({ children }){return(<SidebarLayoutsidebar={<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:
import{StackedLayout}from'@/components/stacked-layout'import{Navbar}from'@/components/navbar'import{Sidebar}from'@/components/sidebar'functionExample({ children }){return(<StackedLayoutnavbar={<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:
We 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.
The 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.
When 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.
Customer
Michael Foster
Event
Bear Hug: Live in Concert
Amount
$150.00 USD
Amount after exchange rate
US$150.00 → CA$199.79
Fee
$4.79 USD
Net
$1,955.00
import{DescriptionDetails,DescriptionList,DescriptionTerm}from'@/components/description-list'functionExample(){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.
More 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.
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.
We worked tirelessly on this one, and are so proud to make this part of your application development process easier.
Check out the Divider documentation — it does have one prop at least.
Catalyst 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.
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 make the developer experience even better.
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 make the developer experience even better.
We just released Headless UI v2.0 for React, which is the culmination of all this work.
We’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.
Use 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
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].
We’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'importclsxfrom'clsx'functionExample(){return(<Field><CheckboxdefaultCheckedclassName={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',)}><CheckmarkIconclassName="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 newfeatures 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.
We’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><labelid="name-label"for="name-input">Name</label><inputid="name-input"aria-labelledby="name-label"aria-describedby="name-description"/><pid="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:
import{Description,Field,Input,Label}from'@headlessui/react'functionExample(){return(<Field><Label>Name</Label><Inputname="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
Shipping details
We currently only ship to North America.
import{Button,Description,Field,Fieldset,Input,Label,Legend,Select}from'@headlessui/react'import{ regions }from'./countries'exportfunctionExample(){const[country, setCountry]=useState(null)return(<formaction="/shipping"><Fieldset><Legend>Shipping details</Legend><Field><Label>Street address</Label><Inputname="address"/></Field><Field><Label>Country</Label><Description>We currently only ship to NorthAmerica.</Description><Selectname="country"value={country}onChange={(event)=>setCountry(event.target.value)}><option></option><option>Canada</option><option>Mexico</option><option>UnitedStates</option></Select></Field><Fielddisabled={!country}><LabelclassName="data-[disabled]:opacity-40">State/province</Label><Selectname="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.
Using 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
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:
We’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.
Use 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]functionExample(){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(<Comboboxvalue={selected}virtual={{options: filteredPeople }}onChange={(value)=>setSelected(value)}onClose={()=>setQuery('')}><div><ComboboxInputdisplayValue={(person)=> person?.name}onChange={(event)=>setQuery(event.target.value)}/><ComboboxButton><ChevronDownIcon/></ComboboxButton></div><ComboboxOptions>{({option: person })=>(<ComboboxOptionkey={person.id}value={person}>{person.name}</ComboboxOption>)}</ComboboxOptions></Combobox>)}
We’re hiring a Design Engineer and Staff Software Engineer to work on some ambitious new projects with us. Both roles are fully remote, with a salary of $275,000 USD.
We’re small on purpose, and we take a lot of pride in punching above our weight. We’re six people, but Tailwind CSS is installed over 30 million times per month, and is used by the world’s biggest companies to build the world’s best websites.
We’re independent and profitable, and we do this because it’s fun. We’re in this to enjoy the actual work we do every day, not to grind it out in hopes of a big exit in the future.
If this sounds like the sort of place you’d like to work, we’d love to hear from you.
We’re hiring a Design Engineer to build ambitious interfaces, prototype new ideas, and push the boundaries of what’s possible with Tailwind CSS.
You’d be responsible for things like:
Design and build ambitious marketing websites for our open-source projects, commercial products, and events like Tailwind Connect.
Design and prototype new features for Tailwind CSS to make sure we’re always using the full potential of the platform.
Create new components and templates for Tailwind UI, taking them all the way from initial concept to shipped.
Enhance our documentation with visual demos to make it easy for people to understand and apply complex CSS features in their work.
Teach and inspire our audience by breaking down interesting things you design and build as articles and social media posts.
Here are some real examples of projects you would have worked on in the last few months:
Design and build the Tailwind Connect microsite — including coming up with a unique Markdown-driven badge design experience.
Craft the first components for Catalyst — our first fully-componentized React UI kit.
Build a tool for capturing videos for our Showcase site — to automate creating preview videos that feel like a real person scrolling through a site.
Build interactive demos to explain dynamic viewport units — to include in the documentation along with the release of Tailwind CSS v3.4.
After you start, you’d work on upcoming projects like:
Design and build an interactive microsite for the Tailwind CSS v4.0 release with thoughtfully crafted demos that communicate the most important improvements in a visual way.
Prototype APIs for scroll-driven animations in Tailwind CSS, finding the right balance between simplicity and flexibility, and making our users feel like they have superpowers.
Explore new color palettes with automatic dark mode support, carefully figuring out the right abstractions for different levels of hierarchy that apply universally to different projects.
Research and design text-shadow support for Tailwind CSS, finally.
This is a fully remote position with a salary of $275,000 USD, open to candidates in the Eastern (UTC-5) to Central European (UTC+1) timezones.
We’re hiring a Staff Software Engineer to work on Tailwind CSS, Headless UI, and our supporting ecosystem of tools like the Tailwind CSS IntelliSense extension for VS Code.
You’d have a wide range of responsibilities, including:
Build and document new features for projects like Tailwind CSS and Headless UI.
Contribute improvements to upstream projects like Lightning CSS, Next.js, and Vite.
Maintain CI workflows and automations for running tests and building releases.
Manage issues and contributions from the community to our open-source repositories.
Define and uphold engineering standards with a focus on code quality, performance, and documentation, to make sure we’re always shipping work we can be proud of.
Manage project priorities and adjust scope to meet project deadlines without sacrifing on top-level goals.
Here are some real examples of projects you would have worked on in the last few months:
Create a Rust library for extracting utility class names — a tool that can scan an entire directory of template files as fast as possible and extract potential class names in parallel.
Write a fast CSS parser in TypeScript — to parse only the exact amount of detail needed by Tailwind’s internals, looking for domain-specific shortcuts to make it significantly faster than existing libraries for our needs.
Fix an issue with scroll-locking in Headless UI — to make sure scrollable elements in dialogs can still be scrolled on iOS.
Campaign for new APIs we need in Vite — prepare a compelling argument for improvements we need to support Tailwind CSS and collaborate with the Vite core team to prototype a solution.
After you start, you’d work on upcoming projects like:
Develop a Tailwind CSS bundler plugin for Next.js — to generate your CSS using the module graph instead of scanning the file system, working with our contacts at Vercel to get it right.
Design a set of IDE integration APIs for Tailwind CSS — first-class, stable, documented APIs that editor teams like JetBrains can rely on to add things like completions, lint warnings, class sorting, and more to their tools.
Design, build, and document a Tooltip component for Headless UI — making sure it’s fully accessible, with a flexible but delightful API.
Build a backwards compatibility layer for Tailwind CSS v4.0 — re-introduce support for JavaScript configuration files, explore codemod tooling, and make sure existing community plugins are as compatible as possible with the new engine.
This is a fully remote position with a salary of $275,000 USD, open to candidates in the Eastern (UTC-5) to Central European (UTC+1) timezones.
Last summer at Tailwind Connect I shared a preview of Oxide — a new high-performance engine for Tailwind CSS that we’ve been working on, designed to simplify the developer experience and take advantage of how the web platform has evolved since Tailwind was first released.
Last summer at Tailwind Connect I shared a preview of Oxide — a new high-performance engine for Tailwind CSS that we’ve been working on, designed to simplify the developer experience and take advantage of how the web platform has evolved in recent years.
The new engine was originally going to ship as a v3.x release, but even though we’re committed to backwards compatibility, this feels so clearly like a new generation of the framework that it deserves to be v4.0.
It’s still early and we’ve got a lot of work to do, but today we’re open-sourcing our progress and tagging the first public v4.0.0-alpha so you can start experimenting with it and help us get to a stable release later this year.
I’ll try keep it brief to save some of the excitement for the stable release, but if you like to play with very early and experimental stuff, there should be plenty of information here to get you going.
The new engine is a ground-up rewrite, using everything we know about the framework now to better model the problem space, making things faster with a lot less code.
Up to 10x faster — we can do a full build of the Tailwind CSS website in 105ms instead of 960ms, or our Catalyst UI kit in 55ms instead of 341ms.
Smaller footprint — the new engine is over 35% smaller installed, even with the heavier native packages we ship like the parts we’ve rewritten in Rust and Lightning CSS.
Rust where it counts — we’ve migrated some of the most expensive and parallelizable parts of the framework to Rust, while keeping the core of the framework in TypeScript for extensibility.
One dependency — the only thing the new engine depends on is Lightning CSS.
Custom parser — we wrote our own CSS parser and designed our own data structures tailored to our needs, making parsing over 2x as fast for us as it was with PostCSS.
Tailwind CSS v4 isn’t just a plugin anymore — it’s an all-in-one tool for processing your CSS. We’ve integrated Lightning CSS directly into the framework so you don’t have to configure anything about your CSS pipeline.
Built-in @import handling — no need to setup and configure a tool like postcss-import.
Built-in vendor prefixing — you don’t have to add autoprefixer to your projects anymore.
Built-in nesting support — no plugins needed to flatten nested CSS, it works out of the box.
Syntax transforms — modern CSS features like oklch() colors and media query ranges are transpiled to syntax with better browser support.
We’re still shipping a PostCSS plugin, but we’re also exploring first-party bundler plugins, and we’re shipping an official Vite plugin with this first alpha release that you can try out today.
We’re looking into the future with Tailwind CSS v4 and trying to build a framework that’s going to feel cutting edge for years to come.
Native cascade layers — we’re using real @layer rules now, which solves a ton of specificity problems we’ve wrestled with in the past.
Explicitly defined custom properties — we use @property to define our internal custom properties with proper types and constraints, making it possible to do things like transition background gradients.
Using color-mix for opacity modifiers — making it easier than ever to use our opacity modifier syntax when using CSS variables for colors, or even adjusting the opacity of currentColor.
Container queries in core — we’ve added support for container queries directly to core, with new @min-* and @max-* variants to support container query ranges.
We’re also working on refreshing our color palette with wide gamut colors, and introducing support for other modern CSS features like @starting-style, anchor positioning, and more.
The new architecture makes it possible to compose together variants that act on other selectors, like group-*, peer-*, has-*, and a new not-* variant we’re introducing for v4.
In earlier releases, variants like group-has-* were explicitly defined in the framework, but now group-* can compose with the existing has-* variant, which can compose with other variants like focus:
There’s no limits to this composability, and you can even write stuff like group-not-has-peer-not-data-active:underline if for some horrible reason that’s what you need to do.
You’ll notice that at least in these early alpha releases, it’s not even possible to configure your content paths. For most projects, you’re never going to need to do this ever again — Tailwind just finds your template files for you.
We do this using one of two ways depending on how you’ve integrated Tailwind into your project:
Using the PostCSS plugin or the CLI, Tailwind will crawl your entire project looking for template files, using a bunch of heuristics we’ve built in to keep things fast, like not crawling directories that are in your .gitignore file, and ignoring binary file formats.
Using the Vite plugin, we rely on the module graph. This is amazing because we know exactly what files you’re actually using, so it’s maximally performant, and with no false positives or negatives. We’re hoping to expand this approach outside of the Vite ecosystem with other bundler plugins in the future.
We’ll introduce a way to configure content paths explicitly in the future for sure, but we’re curious to see how well this automatic approach works for everyone — it’s working awesome in our own projects.
The special @theme directive tells Tailwind to make new utilities and variants available based on those variables, letting you use classes like 3xl:text-neon-lime in your markup:
index.html
<divclass="max-w-lg 3xl:max-w-xl"><h1class="font-display text-4xl">
Data to <spanclass="text-neon-cyan">enrich</span> your online business
</h1></div>
Adding new CSS variables behaves like extend did in earlier versions of the framework, but you can override a whole set of variables by clearing the namespace with syntax like --color-*: initial before defining all of your custom values:
We’re still fine-tuning some of the naming conventions, but you can explore the default theme on GitHub to see what’s available to customize.
If you don’t want to explicitly clear the default theme and would rather start from scratch, you can import "tailwindcss/preflight" and "tailwindcss/utilities" directly to skip importing the default theme:
It also makes it possible to use your theme values when working with UI libraries like Framer Motion, without having to use the resolveConfig() function:
We don’t take breaking changes lightly, but there are a few things we’re doing differently in v4 so far that are worth sharing:
Removed deprecated utilities — we’ve removed utilities we stopped documenting a long time ago like text-opacity-*, flex-grow-*, and decoration-slice in favor of their modern replacements like text-{color}/*, grow-*, and box-decoration-slice.
PostCSS plugin and CLI are separate packages — the main tailwindcss package doesn’t include these anymore since not everyone needs them, instead they should be installed separately using @tailwindcss/postcss and @tailwindcss/cli.
No default border color — the border utility used to default to gray-200, but now it defaults to currentColor like the browser does. We made this change to make it harder to accidentally introduce a wrong gray into your project if you’re using zinc or slate or something else as your main gray.
Rings are 1px by default — the ring utility used to be a 3px blue ring by default, now it’s a 1px ring using currentColor. We find ourselves using the ring-* utilities as an alternative to borders in our projects, and using outline-* for focus rings, so we think making things consistent here is a helpful change.
There are a handful of other really low-level implementation detail changes that might surface in some way in your projects, but nothing deliberate like these changes. If you bump into anything surprising, let us know.
This new engine is a ground-up rewrite, and up until now we’ve been focused entirely on this reimagined developer experience using the new configuration approach.
We put an enormous amount of value in backwards compatibility, and that’s where the bulk of the work lies before we can tag a stable v4.0 release later this year.
Support for JavaScript configuration files — reintroducing compatibility with the classic tailwind.config.js file to make migrating to v4 easy.
Explicit content path configuration — making it possible to tell Tailwind exactly where your templates are when automatic content detection isn’t good enough for your setup.
Support for other dark modes — right now we only support dark mode using media queries, and still need to reimplement the selector and variant strategies.
Plugins and custom utilities — we don’t have support for plugins, or for writing custom utilities that automatically work with variants yet. Obviously we will make this work before a stable release.
Prefix support — there’s no way to configure a prefix for your classes yet, but we’ll bring it back for sure.
Safelists and blocklists — can’t force Tailwind to generate certain classes or prevent it from generating other classes yet.
Support for important configuration — there’s no way to make utilities all generate with !important right now, but we plan to implement it.
Support for the theme() function — this isn’t needed for new projects because you can use var() now, but we’ll implement it for backwards compatibility.
Standalone CLI — we haven’t worked on a standalone CLI for the new engine yet, but will absolutely have it before the v4.0 release.
Aside from that, I’m sure we’ll find a lot of bugs to fix, some exciting new CSS features to sneak in, and refine some of these new APIs that need some more polish before a proper release.
I don’t want to make promises on a specific release timeline, but I’d personally love to mark v4.0 as stable before the summer holiday season kicks in.
We’ve tagged a couple alpha releases already, and you can start playing with it in your projects today.
If you’re using the Tailwind CSS IntelliSense extension for VS Code, make sure you switch to the prerelease version from the extension page, and if you’re using our Prettier plugin, make sure you install the latest version.
If you find an issue, please let us know on GitHub. We really want this thing to be bullet-proof before we tag a stable release and reporting any problems you find will help us a lot.
Today’s the day — we just published the first development preview of Catalyst, our first fully-componentized, batteries-included application UI kit for React, just in time for your holiday hacking sessions.
Today’s the day — we just published the first development preview of Catalyst, just in time for your holiday hacking sessions.
Catalyst is our first fully-componentized, batteries-included application UI kit — real React components with thoughtfully designed APIs that build on each other to create a real component architecture, the same way we’d do it in a real application.
Check out the live demo, read the documentation, and if you’re a Tailwind UI All-Access customer, download it and try it out in a new project today.
Catalyst is currently in development preview and there’s a lot more to come, but we’re releasing it today so you can play with it right away as we continue to build new components and find ways to make it an even better experience.
With Catalyst, we set out to build a UI kit that tomorrow’s Stripe or Linear would feel good about using to build their products — design-obsessed teams who want to own their UI components, and would never choose an off-the-shelf library.
So it’s not a dependency you install, instead you download the source and copy the components into your own project where they become the starting point for your own component system:
Want to change the border radius on your buttons? Just open button.tsx and change some classes. You don’t need to open a GitHub issue and try to convince us to expose a new configuration option.
Catalyst is a “disappearing UI kit” — six months after you’ve installed it, you should almost forget it wasn’t you who built the original components.
Getting the visual style right on a project like this is hard. We went into it with a few goals:
Be competitive — we wanted to design something that could hold its own next to some of the nicest interfaces on the web today.
Be timeless — we didn’t want to design something that would look dated in 6 months because it leaned too hard into specific trends.
Be productive — whatever we designed needed to feel fast and efficient to real users, not just look great in a Dribbble shot.
It took a lot of work and there were a lot of trade-offs to balance, but I’m really in love with where we ended up:
To be competitive, we invested in lots of details like subtle backdrop blurs on dropdown menus, perfecting the way shadows and borders blend with each other on form controls, and thoughtful use of animation in things like dialogs and toggle switches.
To be timeless, we tried to strike the right balance between flat and skeuomorphic design, with just enough depth cues that our components will look great even if the trends change a bit in either direction.
We also took inspiration from the browser, and used unopinionated blue focus rings to avoid picking a treatment that might soon look out of fashion.
To be productive, we worked carefully to make sure there was still plenty of whitespace, but that the UI was still dense enough to fit plenty of information on the screen.
We also limited our use of transitions and animations only to places where it felt important, and even then tried to keep them fast so it never feels like you’re waiting on the UI.
Catalyst also ships with full dark mode support, and anything you build with Catalyst components automatically adapts between light and dark modes.
It’s not obvious, but there are a ton of little details we had to change to make things look their best in dark mode, like adjusting shadows, changing outer rings to inner rings to mimic the change in lighting, and more.
We spent a lot of time working on the component APIs, trying very hard to make things really easy to drop in and use right away, without compromising on flexibility.
It’s common for UI libraries to use APIs like this:
JSX
functionExample(){return(<TextFieldname="product_name"label="Product name"description="Use the name you'd like people to see in their cart."/>)}
But with all the props living on the same component, it starts to get difficult to do things like add a class just to the <input> element itself.
Ultimately that led us to APIs that closely mirrored HTML, where it’s rare that a single component renders more than one element.
Creating a text field with Catalyst looks like this for example:
JSX
import{Description,Field,Label}from'@/components/fieldset'import{Input}from'@/components/input'functionExample(){return(<Field><Label>Product name</Label><Description>Use the name you'd like people to see in their cart.</Description><Inputname="product_name"/></Field>)}
By keeping things composable like this, it makes it really easy to do things like constrain the width of the input, without constraining the width of any of the other elements:
JSX
import{Description,Field,Label}from'@/components/fieldset'import{Input}from'@/components/input'functionExample(){return(<Field><Label>Product name</Label><Description>Use the name you'd like people to see in their cart.</Description><Inputname="product_name"/><Inputname="product_name"className="max-w-sm"/></Field>)}
It also makes it easy to move the description below the input, instead of above:
JSX
import{Description,Field,Label}from'@/components/fieldset'import{Input}from'@/components/input'functionExample(){return(<Field><Label>Product name</Label><Description>Use the name you'd like people to see in their cart.</Description><Inputname="product_name"className="max-w-sm"/><Description>Use the name you'd like people to see in their cart.</Description></Field>)}
It took a lot of experimenting to figure out the right way to make these APIs work, especially around details like adding layout styles to the right children, but the payoff was worth it, and these components are really a delight to use.
We released the first version of Headless UI back in the summer of 2020, but it’s been just over a year now since the last significant feature release because of all the work we’ve been focused on with Tailwind CSS itself.
Catalyst was the perfect excuse to get our hands dirty with Headless UI again, and we quickly found lots of ways to improve the project to simplify the code in Catalyst itself.
We just published Headless UI v2.0.0-alpha.1, which includes a ton of new stuff:
Built-in anchor positioning — using Floating UI, components like Menu, Listbox, and more can now automatically position their popovers to be anchored to their trigger, adapting as needed to changes in the viewport.
Headless checkbox component — we’ve added a headless Checkbox component to complement our existing RadioGroup component, making it easy to build totally custom checkbox controls.
HTML form components — we’ve added Input, Select, Textarea, Label, Description, Fieldset, and Legend components that handle all of the ID generation and aria-* attribute mapping you need to do to connect form fields together.
Improved hover and focus-visible detection — using hooks from the awesome React Aria library under the hood, Headless UI now adds smarter data-hover and data-focus attributes to your controls that behave more consistently across different devices than the native pseudo-classes.
Combobox list virtualization — the next version of Headless UI can now handle giant lists of combobox options with no performance issues.
…with plenty of other improvements to come, including a date picker, tooltips, and more.
These improvements are React-only for now during this early alpha period, but we plan to bring all of these improvements to Vue as well before tagging v2.0.
We’ll have documentation published for this stuff really soon, but couldn’t resist getting Catalyst out before the holidays, even if it meant publishing the Headless UI docs a few days later.
More work went into getting everything we’re releasing today just right than you could ever imagine, but we’re eager for feedback and ways it could be improved, so build some stuff with it and let us know what you think.
We’re going to take a couple weeks to recharge over the holidays, but we’ll be right back into Catalyst in the new year, working on new components like application layouts, comboboxes, command palettes, tooltips, and more.
There’s nothing like building a major new product for finding all the features you wish you had in your own tools, so we capitalized on some of that inspiration and turned it into this — Tailwind CSS v3.4.
There’s nothing like building a major new product for finding all the features you wish you had in your own tools, so we capitalized on some of that inspiration and turned it into this — Tailwind CSS v3.4.
As always the improvements range from things you’ve been angry about for years, to supporting CSS features you’ve never even heard of and probably can’t even use at work.
All the good stuff is in that list, but check out the release notes for a couple more details that weren’t exciting enough to earn a spot in this post.
Upgrade your projects by installing the latest version of tailwindcss from npm:
$ npminstall tailwindcss@latest
Or try out all of the new features on Tailwind Play, right in your browser.
When the vh unit was added to browsers we all got so excited — finally a way to build full-height application layouts and stuff without drilling height: 100% through 17 layers of DOM! But mobile devices and their damn disappearing menu bars spoiled all the fun, effectively making the vh unit just a cruel reminder of a future that could’ve been so great.
Well we’ve got a new future now — dvh, lvh, and svh are designed to accommodate that disappearing browser chrome and Tailwind CSS v3.4 supports them out of the box:
Scroll up and down in the viewport to hide/show the browser UI
tailwindcss.com
h-dvh
<divclass="h-dvh"><!-- ... --></div>
We’ve added the following new classes by default:
Class
CSS
h-svh
height: 100svh
h-lvh
height: 100lvh
h-dvh
height: 100dvh
min-h-svh
min-height: 100svh
min-h-lvh
min-height: 100lvh
min-h-dvh
min-height: 100dvh
max-h-svh
max-height: 100svh
max-h-lvh
max-height: 100lvh
max-h-dvh
max-height: 100dvh
If you need other values, you can always use arbitrary values too like min-h-[75dvh].
Browser support is pretty great for these nowadays, so unless you need to support Safari 14 you can start using these right away.
The :has() pseudo-class is the most powerful thing that’s been added to CSS since flexbox. For the first time ever, you can style an element based on its children, not just based on its parents. It even makes it possible to style based on subsequent siblings.
Here’s an example where the parent gets a colored ring if the radio button inside of it is checked:
<labelclass="has-[:checked]:ring-indigo-500 has-[:checked]:text-indigo-900 has-[:checked]:bg-indigo-50 .."><svgfill="currentColor"><!-- ... --></svg>
Google Pay
<inputtype="radio"class="accent-indigo-500 ..."/></label>
I feel like I’ve found a new use-case for :has() every week while working on this new UI kit we’ve been building for the last few months, and it’s replaced a crazy amount of JavaScript in our code.
For example, our text inputs are pretty complicated design-wise and require a little wrapper element to build. Without :has(), we had no way of styling the wrapper based on things like the :disabled state of the input, but now we can:
This one is pretty bleeding edge but as of literally today it’s now supported in the latest version of all major browsers. Give it a few weeks for any Firefox users to install today’s update and we should be able to go wild with it.
Generally I’d recommend just styling the children directly, but this can be useful when you don’t control those elements or need to make a conditional tweak because of the context the element is used in.
It can be composed with other variants too, for instance hover:*:underline will style any child when the child is hovered.
Here’s a cool way we’re using that to conditionally add layout styles to different child elements in the new UI kit we’re working on:
JSX
functionField({ children }){return(<divclassName="data-[slot=description]:*:mt-4 ...">{children}</div>)}functionDescription({ children }){return(<pdata-slot="description"...>{children}</p>)}functionExample(){return(<Field><Label>First name</Label><Input/><Description>Please tell me you know your own name.</Description></Field>)}
See that crazy data-[slot=description]:*:mt-4 class? It first targets all direct children (that’s the *: part), then filters them down to just items with a data-slot="description" attribute using data-[slot=description].
This makes it easy to target only specific children, without having to drop all the way down to a raw arbitrary variant.
Looking forward to seeing all the horrible stuff everyone does to make me regret adding this feature.
We’ve wanted to add this forever but have always been hung up on the exact name — size-* felt like so much to type compared to w-* or h-* and s-* felt way too cryptic.
After using it for a few weeks though I can say decisively that even with the longer name, it’s way better than separate width and height utilities. Super convenient, especially if you’re combining it with variants or using a complex arbitrary value.
How much time have you spent fiddling with max-width or inserting responsive line breaks to try and make those little section headings wrap nicely on your landing pages? Well now you can spend zero time on it, because the browser can do it for you with text-wrap: balance:
Beloved Manhattan soup stand closes
New Yorkers are facing the winter chill with less warmth this year as the city's most revered soup stand unexpectedly shutters, following a series of events that have left the community puzzled.
<article><h3class="text-balance ...">Beloved Manhattan soup stand closes<h3><p>New Yorkers are facing the winter chill...</p></article>
We’ve also added text-pretty which tries to avoid orphaned words at the end of paragraphs using text-wrap: pretty:
Beloved Manhattan soup stand closes
New Yorkers are facing the winter chill with less warmth this year as the city's most revered soup stand unexpectedly shutters, following a series of events that have left the community puzzled.
<articleclass="text-pretty ..."><h3>Beloved Manhattan soup stand closes<h3><p>New Yorkers are facing the winter chill...</p></article>
The nice thing about these features is that even if someone visits your site with an older browser, they’ll just fallback to the regular wrapping behavior so it’s totally safe to start using these today.
Subgrid is a fairly recent CSS feature that lets an element sort of inherit the grid columns or rows from its parent, make it possible to place its child elements in the parent grid.
We’re using subgrid in the new UI kit we’re working on for example in dropdown menus, so that if any item has an icon, all of the other items are indented to keep the text aligned:
When none of the items have an icon, the first column shrinks to 0px and the text is aligned all the way to left.
Check out the MDN documentation on subgrid for a full primer — it’s a bit of a tricky feature to wrap your head around at first, but once it clicks it’s a game-changer.
We’ve finally extended the min-width, max-width, and min-height scales to include the full spacing scale, so classes like min-w-12 are actually a real thing now:
HTML
<divclass="min-w-12"><!-- ... --></div>
We should’ve just done this for v3.0 but never really got around to it — I’m sorry and you’re welcome.
We’ve also added new forced-color-adjust-auto and forces-color-adjust-none utilities to control how forced colors mode affects your design:
HTML
<fieldset><legend>Choose a color</legend><divclass="forced-color-adjust-none ..."><label><inputclass="sr-only"type="radio"name="color-choice"value="white"/><spanclass="sr-only">White</span><spanclass="size-6 rounded-full bg-white"></span></label><label><inputclass="sr-only"type="radio"name="color-choice"value="gray"/><spanclass="sr-only">Gray</span><spanclass="size-6 rounded-full bg-gray-300"></span></label><!-- ... --></div></fieldset>
These should be used pretty sparingly, but they can be useful when it’s critical that something is rendered in a specific color no matter what, like choosing the color of something someone is buying in an online store.
To learn more about all this forced colors stuff, I recommend reading “Forced colors explained: A practical guide” on the Polypane blog — by far the most useful post I’ve found on this topic.
If you’ve been paying close attention, you might be wondering about Oxide, the engine improvements we previewed at Tailwind Connect this summer.
We’d originally slated those improvements for v3.4, but we have a few things still to iron out and so many of these other improvements had been piling up that we felt it made sense to get it all out the door instead of holding it back. The Oxide stuff is still coming, and will be the headlining improvement for the next Tailwind CSS release in the new year.
In the mean time, dig in to Tailwind CSS v3.4 by updating to the latest version with npm:
$ npminstall tailwindcss@latest
With :has() and the new * variant, your HTML is about to get more out of control than ever.
We just released Heroicons v2.1 which includes a brand new micro style — a full set of almost three hundred 16×16 icons designed for tighter, higher density UIs.
We’ve wanted to do a set this size for a long time, but it wasn’t until we started working on a new React UI kit project earlier this year that we finally needed them badly enough to bite the bullet and design them.
In Catalyst (our new UI kit), we’ve tried to really thread the needle in terms of giving things enough room to breathe but also keeping things dense enough that the applications you build with it actually feel productive to use.
We’re using lots of 14px text which looks great in the UI, but when we tried to incorporate our existing 20×20 icons, they felt just a tiny bit too big and unbalanced.
Most people would have probably just scaled the icons down and moved on, but we’re not most people, for better or for worse.
Icons always turn out a lot sharper when you design them for the exact size they’re going to be used, so we began the process of redrawing every icon from scratch, carefully trimming down the amount of detail on an icon-by-icon basis to make sure they render nice and crisp at their intended size.
About a month later we had a brand new set of 288 icons, meticulously crafted for higher density interfaces like what we’ve been working on with Catalyst.
We just released Studio — a beautiful new agency website template we’ve been working on for the last couple of months for Tailwind UI.
We just released Studio — a beautiful new agency website template we’ve been working on for the last couple of months for Tailwind UI.
We built it with Next.js, MDX, and of course Tailwind CSS, and it’s the first template we’ve published using the new Next.js App Router.
Designing an agency template is an interesting project, because creative agencies commonly use their own website to show off some really flashy, bespoke ideas, and using a template just kind of feels strange when the goal is to show what your own company is capable of.
So we tried to approach this one with two goals in mind to actually make it useful to people:
Teach people how to do some of the cool stuff you see on flash agency sites — I’ve always believed our templates are just as (if not more) valuable as an educational resource than as simply templates, so we wanted to use this template as an opportunity to show off how we’d build a lot of the cool interactive and animated details you see on these sorts of sites.
Design it for agencies that don’t sell design — there are a lot of agencies out there who just focus on engineering work, and a lot of the time those companies struggle to stand out design-wise. We tried to design this template in a way that it didn’t depend on tons of screenshots of design work and stuff to look good, so that an agency that focuses on code could use it as a starting point for their own site.
I think what we came up with nailed these two goals and I’m really proud of how it all turned out.
Check 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.
One of the unspoken rules of agency websites is that they’ve gotta be flashy. We didn’t go full replace-the-mouse-cursor or render-the-entire-site-with-WebGL but we did look for opportunities to tastefully introduce animations and interactivity wherever we could.
For instance, we built a light declarative component-based API around some features of Framer Motion to make it easy to do scroll-triggered entrance animations:
The authoring experience for these types of animations turned out really nice — just wrap the stuff you want to fade in with a FadeIn or FadeInStagger component and you’re in business:
functionClients(){return(<divclassName="mt-24 rounded-4xl bg-neutral-950 py-20 sm:mt-32 sm:py-32 lg:mt-56"><Container><FadeInclassName="flex items-center gap-x-8"><h2className="text-center font-display text-sm font-semibold tracking-wider text-white sm:text-left">
We’ve worked with hundreds of amazing people
</h2><divclassName="h-px flex-auto bg-neutral-800"/></FadeIn><FadeInStaggerfaster><ulrole="list"className="mt-10 grid grid-cols-2 gap-x-8 gap-y-10 lg:grid-cols-4">{clients.map(([client, logo])=>(<likey={client}><FadeIn><Imagesrc={logo}alt={client}unoptimized/></FadeIn></li>))}</ul></FadeInStagger></Container></div>)}
We also added this nice little animation to the logo where the mark is filled with a solid color on hover:
This little detail looks small but interestingly you can’t really do it without client-side navigation, because the animation would re-run when clicking the logo to go back to the homepage. Using a framework like Next.js, we’re able to keep the logo filled in while hovering, even across URL changes, which feels a lot nicer.
The menu drawer animation turned out really nice as well, pushing the whole page down when it opens:
If you look closely, the logo and button don’t just naively change color either — it’s actually driven precisely by the position of the sheet that’s sliding down, and the logo is actually partially white and partially black at the same time when the edge of the sheet is intersecting with it.
Another detail I really love is this interaction we came up with for the images on the case study pages:
We wanted the whole site to feel black and white, but showing black and white images all of the time didn’t feel right. So we came up with this treatment where the image starts off black and white, and the saturation animates back in as the image gets close to the center of the screen when scrolling. We also show the full color image on hover.
We were also careful to try and implement all of these animations in a way that’s mindful of people with vestibular motion disorders and are sensitive to these types of big animations. Using Framer Motion’s useReducedMotion hook and the motion-safe variant in Tailwind, we do things like conditionally disable the navigation menu animation, and limit the scroll-driven entrance animations to opacity only so things aren’t moving on the screen.
Studio includes support for both case studies and blog posts, and as you might have guessed if you’ve played with any of our other templates, we used this an excuse to integrate MDX into the project.
Here’s an example of what a basic case study looks like — authored mostly in markdown with some common metadata and support for custom components mixed in to the content:
importlogofrom'@/images/clients/phobia/logomark-dark.svg'importimageHerofrom'./hero.jpg'importimageJennyWilsonfrom'./jenny-wilson.jpeg'exportconst caseStudy ={client:'Phobia',title:'Overcome your fears, find your match',description:'Find love in the face of fear — Phobia is a dating app that matches users based on their mutual phobias so they can be scared together.',summary:['Find love in the face of fear — Phobia is a dating app that matches users based on their mutual phobias so they can be scared together.','We worked with Phobia to develop a new onboarding flow. A user is shown pictures of common phobias and we use the microphone to detect which ones make them scream, feeding the results into the matching algorithm.',],
logo,image:{src: imageHero },date:'2022-06',service:'App development',testimonial:{author:{name:'Jenny Wilson',role:'CPO of Phobia'},content:'The team at Studio went above and beyond with our onboarding, even finding a way to access the user’s microphone without triggering one of those annoying permission dialogs.',},}exportconst metadata ={title:`${caseStudy.client} Case Study`,description: caseStudy.description,}
## OverviewNoticing incredibly high churn, the team at Phobia came to the conclusion that, instead of having a
fundamentally flawed business idea, they needed to improve their onboarding process.Previously users selected their phobias manually but this led to some users selecting things they
weren’t actually afraid of to increase their matches.To combat this, we developed a system that displays a slideshow of common phobias during
onboarding.We then use malware to surreptitiously access their microphone and detect when they
have audible reactions.We measure the pitch, volume and duration of their screams and feed that
information to the matching algorithm.The next phase is a VR version of the onboarding flow where users are subjected to a series of
scenarios that will determine their fears.We are currently developing the first scenario, working
title: “Jumping out of a plane full of spiders”.
## What we did
<TagList><TagListItem>Android</TagListItem><TagListItem>iOS</TagListItem><TagListItem>Malware</TagListItem><TagListItem>VR</TagListItem></TagList><Blockquoteauthor={{name:'Jenny Wilson',role:'CPO of Phobia'}}image={{src: imageJennyWilson }}>
The team at Studio went above and beyond with our onboarding, even finding a
way to access the user’s microphone without triggering one of those annoying
permission dialogs.
</Blockquote><StatList><StatListItemvalue="20%"label="Churn rate"/><StatListItemvalue="5x"label="Uninstalls"/><StatListItemvalue="2.3"label="App store rating"/><StatListItemvalue="8"label="Pending lawsuits"/></StatList>
All of the typography styles for this template are totally custom and we took a bit of a different approach this time than we have in the past — instead of writing a bunch of complex CSS to avoid our typography styles clashing with any custom components in the MDX, we created a little remark plugin called remark-rehype-wrap that makes it possible to wrap chunks of Markdown content with a wrapper element.
This way, we could wrap anything that was vanilla Markdown content with a typography class, but make sure any custom components in the document were simply not wrapped, rather than try to craft the CSS in such a way that it ignores those parts of the tree.
Both approaches totally work but it’s always fun to try new ideas and see what you learn. I’m curious to see what a solution based on the new style queries feature coming to CSS might look like in the future too!
So that’s Studio! Pull it down, tear it apart, and see if you learn a couple of new tricks.
Like 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 years to come.
Last month, over 200 people got together in my hometown of Cambridge, Ontario to hang out, talk shop, and get a peek behind the curtain at some of the new stuff we’ve been working on.
Tailwind Connect started out as an idea for throwing a little local meetup while the team was in town, but naturally ballooned into us renting a massive hall, hiring a crew of four videographers, and racking up a catering bill that put my own wedding to shame.
But even though we got a little carried away with the production, we really tried to make it feel more like a meetup than a full-blown conference. We ran the event in the evening and kept it to just one presentation, leaving as much time as possible for people to just hang out and connect over pizza and beer.
We originally expected it to be a “drive there after work” local-heavy event, but almost half of the people who attended traveled by plane to get here. So to help everyone make the most of their time here, we set up a Discord server for the event so people traveling in could make plans with each other, and rented out the patio at the Foundry Tavern next door to the event space for the afternoon to give people a reason to meet up early and make some new friends.
We opened up the doors for the event around 5:30pm and started the night with an hour long opening reception, where people could hang out and talk and enjoy some canapés.
One detail I thought we really nailed was the badges — when you bought a ticket we gave you a basic markdown field where you could write anything you wanted about yourself that might help people start interesting conversations with you.
I found myself looking at everyone’s badges all night and it was an awesome way to immediately break the ice and have something to talk about.
At around 6:30pm everyone took a seat for the keynote presentation. I kicked it off with a bit of history on Tailwind CSS as a project, and how it got to where it is today.
Six years ago it was just a couple of stylesheets I was copying and pasting from project to project. Today it’s downloaded over 25 million times per month by millions of developers, and is used by some of the world’s biggest companies to build some of the world’s biggest websites. It was really special to me to finally get a chance to celebrate how far we’ve come in person with so many people from the community.
After that, Sam Selikoff walked through some brilliant demos showing off some of the coolest new features in CSS. He demoed things like accent color, fluid typography, headline balancing, container queries, and even masonry grid, all using Tailwind CSS and the utility-first workflow.
Sam is an awesome friend, I asked him to come up for the event and be a part of the keynote without really even having a plan for what I was going to ask him to do at all, and he put together his whole presentation in about 36 hours after he arrived and absolutely nailed it. No slides, all live-coding, and crushed it like a pro. Highly recommend his YouTube channel and training site Build UI — dude is a fantastic teacher.
Next, I gave a sneak peek at Oxide, the next evolution of the Tailwind CSS engine.
Oxide is a lot of different pieces but they all boil down to two goals — improved performance, and simplifying the developer experience.
We’re making Tailwind more of an all-in-one CSS processing tool by integrating Lightning CSS, which means that in the next version of Tailwind, things like importing other CSS files, nesting, vendor prefixes, and syntax transforms for future CSS features will just work — no need to install or configure any additional tooling like autoprefixer or postcss-import.
Lightning CSS is written in Rust which makes it extremely fast, and we’re dipping our toes into Rust ourselves too by rewriting some of most critical paths, like scanning all of your template files for class names.
With these improvements, we’re seeing build times drop by over 50% in real-world projects, bringing the production build time down to around 150ms for even our largest projects.
Oxide is also going to bring a simplified configuration experience. We’re adding automatic content detection so you won’t need to configure the paths to all of your template files anymore, and making it even easier to add Tailwind to your own CSS by replacing the need for all of the @tailwind directives with a simple @import "tailwindcss" call at the top of your CSS file.
I also shared a very early look at an idea we’re exploring for configuring Tailwind CSS right in your CSS file instead of in JavaScript. With CSS-based configuration, automatic content detection, and the simplified import story, configuring Tailwind with some custom colors and fonts might look as simple as this in the future:
We’re planning to introduce all of this stuff without any breaking changes, and many of these improvements (including Lightning CSS integration and our Rust-based template parser) will be available in Tailwind CSS v3.4 in the next couple of months.
I’ll definitely write about this stuff in a lot more detail closer to release time, but watch the keynote if you want to take a closer look before then.
We closed out the keynote with a preview of Catalyst, the new React UI kit we’ve been working on since late last year.
This is something that’s been on our roadmap for two years at this point, and after many months of ironing out the best approach, we finally broke ground on it last October.
Catalyst is a batteries-included component system with thoughtfully designed APIs, and includes all of the basic building blocks you need to build your own applications. It inclues things like buttons, form controls, dialogs, slide-overs, tables, dropdowns, and more.
The biggest difference between Catalyst and our existing application UI component examples is that the components in Catalyst are all wired together like they’d be in a real project, instead of being isolated copy and paste code snippets.
But just like with our site templates, the code in Catalyst is yours. If you want to tweak something, you just open up the file and tweak it — it’s not a library you install via npm.
Think of it like a starting point for your own component system. You download the latest version from us, copy the contents of the /components directory into your project, then start building.
If you need to change something, change it.
When you want to create your own new components, create them.
The whole idea is that 6 months down the road the codebase feels so yours that you’ll almost forget you kickstarted it with Catalyst in the first place.
After introducing the project and giving everyone a bit of a tour, I handed things off to Steve Schoger who did an amazing job walking through all of the little design details that went into making Catalyst feel like such a polished design system.
I wrapped things up by showing off a couple of advanced Tailwind CSS tricks we had to use to implement some of those design details, like how to implement responsive transitions with Framer Motion using CSS variables and Tailwind’s arbitrary property feature.
Catalyst is still a work-in-progress so don’t hold me to it, but with any luck we’ll have an early version and more details to share with Tailwind UI customers in the next month or so.
For the next three hours we got to hang out in the event space enjoying some refreshments, and getting to meet all of the people who made the trip to attend.
I mentioned it in the keynote, but this was the first time in the history of the project that we’d ever gotten a bunch of Tailwind fans into the same room, and it was a really special experience for me to finally meet so many people who have gotten so much out of using the framework. It’s easy to sort of underappreciate the impact of the work we do here when everything is just on GitHub and Twitter, and to see it manifested in physical space with real people just left me beaming with pride.
It’s really an honor to work on these projects and be able to help so many people have more fun building things for the web. Looking forward to doing another event like this in the future — a major life highlight for me for sure.
Over the last four months we’ve probably done more work on Tailwind UI than we ever have, and having finally wrapped up what we set out to achieve I’m excited to lay it all out for you.
I haven’t shared a Tailwind UI update since we released the Protocol template in December, but that’s not because we haven’t been busy.
Over the last four months we’ve probably done more work on Tailwind UI than we ever have, and having finally wrapped up what we set out to achieve I’m excited to lay it all out for you!
Just a few days ago we released Commit, a brand new changelog template we designed for Tailwind UI — built of course with Tailwind CSS and Next.js.
Public changelogs have become a really popular way to keep people in the loop about what you’ve been working on, and to stay accountable and build your shipping muscles. They aren’t a new concept by any means of course, but I don’t think it was until Linear started publishing to their changelog site that others got excited about using them almost as an alternative to a company blog.
Commit is our take on the modern product changelog, designed as a single page website that can act as both your project homepage and a feed of everything you’ve been working on.
Like all of our templates, it’s loaded with features and details that make it a delight to experience and a pleasure to work on:
Light and dark mode support, because you can’t make anything a developer might read without optimizing it for reading at 2am with the lights off.
Hand-crafted typography styles, meticulously chosen font sizes, spacing, list styles, and more, designed specifically for this template.
Built-in syntax highlighting, powered by Shiki, and easy to customize with a few CSS variables.
Single-file editing experience, every entry lives in a single MDX file, so updating your changelog feels as lightweight as updating a CHANGELOG.md file in an open-source project.
Beautiful animations and effects, this time powered by Motion One, making it a great resource for studying how to pull off these sorts of tricks with a cutting-edge new library.
As always it’s easy to jump into the code and make it your own — here we’ve just tweaked a handful of colors and it feels like a totally different website:
Check out the live demo for the full experience, and if you’re already the proud owner of a Tailwind UI all-access license, download a copy of the template to use it in your next project or just to study the source code to learn a new trick or two.
Design moves fast and with it being over three years since we first released Tailwind UI, we felt like it deserved to be put under the microscope and make sure it still felt like our best work.
We were pleasantly surprised to discover that yes, we have actually gotten better at design over the past three years, so we spent four months heads down making every component and category as pixel-perfect as we possibly could with our newfound powers.
Once we came out of our cave to see the sunlight again we had hundreds of redesigned components, dozens of totally new ideas put together, and a fresh batch of page examples to show them all off.
Here’s a run down of some of the types of improvements we made.
A lot of the component patterns in Tailwind UI are really timeless ideas, but as design trends change and we become better designers, the specific implementations of those patterns can start to feel like it’s from another era.
We went through all of the components one-by-one and found lots of patterns we wanted to take another stab at and did our best to bring them into 2023.
Take a look at the Hero Sections category for some great examples of what these refreshed patterns look like.
A lot of components didn’t really need a full on redesign as much as they needed just a little bit of extra polishing.
We went through tons of components making subtle improvements to the spacing, typography, and contrast, and the results just feel so much sharper and cleaner.
The example above is from the Description Lists category — check it out there if you want to see it in its full browser-rendered glory.
As we were going through all of the existing components, we kept coming up with new ideas that felt like they were missing from the original set of patterns.
So we designed tons of brand new components, trying to fill as many holes that stood out to us as we could.
Lots of categories more than doubled in size, like the Feature Sections category which is loaded with really killer new ideas.
It feels like almost every new website I see these days is dark by default, so it felt like we had a moral obligation to give you some more examples optimized for dark backgrounds.
One of examples I really like are these new dark badges — there’s not much to them really but that little bit of opacity on the background color is such a nice effect for dark designs.
Finally we took all of this new stuff and put together a bunch of brand new page examples to show them off, including the application UI example everyone has been bugging us for from the screenshots in some of our marketing components.
Check out the updated Home Screens category for example to see some of these new designs.
So there you go, without a doubt our biggest Tailwind UI update of all time. We’ve been dripping out these improvements slowly since January and it’s all captured in the Tailwind UI changelog so check that out if you want to dig in to what’s changed in more detail.
Next up for us — digging in to a ton of ideas we have for Tailwind CSS v4.0, and exploring our first Next.js application starter kit. Excited to share more in the coming weeks!
Tailwind CSS v3.3 is here — bringing a bunch of new features people have been asking for forever, and a bunch of new stuff you didn’t even know you wanted.
Tailwind CSS v3.3 is here — bringing a bunch of new features people have been asking for forever, and a bunch of new stuff you didn’t even know you wanted.
That covers the most exciting stuff, but check out the release notes for an exhaustive list of every single little improvement we’ve made since the last release.
Upgrading your projects is as easy as installing the latest version of tailwindcss from npm:
npminstall-D tailwindcss@latest
You can also try out all of the new features on Tailwind Play, right in your browser.
One of the most common feature requests we’ve had over the years is to add darker shades for every color — usually because someone is building a dark UI and just wants more options down in that dark end of the spectrum.
Well wish granted — in Tailwind CSS v3.3 we’ve added a new 950 shade for every single color.
In the grays they act as basically a tinted black, which is great for ultra dark UIs:
And in the rest of the color spectrum we optimized 950 for high contrast text and tinted control backgrounds:
Believe it or not the hardest part about this project was convincing ourselves to be okay with having 11 shades per color. Trying to make that look good in the color palette documentation was a nightmare.
Also pour one out for the 50 shades of gray jokes we used to be able to make.
When you run npx tailwindcss init, we’ll detect if your project is an ES Module and automatically generate your config file with the right syntax.
You can also generate an ESM config file explicitly by using the --esm flag:
npx tailwindcss init --esm
To generate a TypeScript config file, use the --ts flag:
npx tailwindcss init --ts
A lot of people assume this is easy because they’re writing their own code in ESM already (even if it’s being transpiled by their build tool) but it’s actually pretty tricky — we literally have to transpile the config file for you on the fly.
It’s a bit easier to understand why this has to happen when you think of the TypeScript case, because of course Tailwind is distributed as JavaScript, and it can’t magically import an uncompiled TypeScript file.
We’re handling this with the wonderful jiti library under the hood, and using Sucrase to transpile the code with the best possible performance while keeping the installation footprint small.
We’ve made it possible to style multi-directional websites using our LTR and RTL variants for a while, but now you can use logical properties to do most of this styling more easily and automatically.
Using new utilities like ms-3 and me-3, you can style the start and end of an element so that your styles automatically adapt in RTL, instead of writing code like ltr:ml-3 rtl:mr-3:
Here’s a full list of all of the new utilities we’ve added and what they map to:
New class
Properties
Physical counterpart (LTR)
start-*
inset-inline-start
left-*
end-*
inset-inline-end
right-*
ms-*
margin-inline-start
ml-*
me-*
margin-inline-end
mr-*
ps-*
padding-inline-start
pl-*
pe-*
padding-inline-end
pr-*
rounded-s-*
border-start-start-radius border-end-start-radius
rounded-l-*
rounded-e-*
border-start-end-radius border-end-end-radius
rounded-r-*
rounded-ss-*
border-start-start-radius
rounded-tl-*
rounded-se-*
border-start-end-radius
rounded-tr-*
rounded-ee-*
border-end-end-radius
rounded-br-*
rounded-es-*
border-end-start-radius
rounded-bl-*
border-s-*
border-inline-start-width
border-l-*
border-e-*
border-inline-end-width
border-r-*
border-s-*
border-inline-start-color
border-l-*
border-e-*
border-inline-end-color
border-r-*
scroll-ms-*
scroll-margin-inline-start
scroll-ml-*
scroll-me-*
scroll-margin-inline-end
scroll-mr-*
scroll-ps-*
scroll-padding-inline-start
scroll-pl-*
scroll-pe-*
scroll-padding-inline-end
scroll-pr-*
These should save you a ton of code if you regularly build sites that need to support both LTR and RTL languages, and you can always combine these with the ltr and rtl variants when you need more control.
We’ve included every value from 0% to 100% in steps of 5 out of the box, but you can of course use arbitrary values to get exactly the effect you want:
We released our official line-clamp plugin just over two years ago and even though it uses a bunch of weird deprecated -webkit-* stuff, it works in every browser and it’s going to work forever, so we decided to just bake it into the framework itself.
Boost your conversion rate
Nulla dolor velit adipisicing duis excepteur esse in duis nostrud occaecat mollit incididunt deserunt sunt. Ut ut sunt laborum ex occaecat eu tempor labore enim adipisicing minim ad. Est in quis eu dolore occaecat excepteur fugiat dolore nisi aliqua fugiat enim ut cillum. Labore enim duis nostrud eu. Est ut eiusmod consequat irure quis deserunt ex. Enim laboris dolor magna pariatur. Dolor et ad sint voluptate sunt elit mollit officia ad enim sit consectetur enim.
Lindsay Walton
<article><div><timedatetime="2020-03-16"class="block text-sm/6 text-gray-600">Mar 10, 2020</time><h2class="mt-2 text-lg font-semibold text-gray-900">Boost your conversion rate</h2><pclass="line-clamp-3 mt-4 text-sm/6 text-gray-600"> Nulla dolor velit adipisicing duis excepteur esse in duis nostrud occaecat mollit incididunt deserunt sunt. Ut ut sunt laborum ex occaecat eu tempor labore enim adipisicing minim ad. Est in quis eu dolore occaecat excepteur fugiat dolore nisi aliqua fugiat enim ut cillum. Labore enim duis nostrud eu. Est ut eiusmod consequat irure quis deserunt ex. Enim laboris dolor magna pariatur. Dolor et ad sint voluptate sunt elit mollit officia ad enim sit consectetur enim.
</p></div><divclass="mt-4 flex gap-x-2.5 text-sm font-semibold leading-6 text-gray-900"><imgsrc="..."class="h-6 w-6 flex-none rounded-full bg-gray-50"/> Lindsay Walton
</div></article>
So when you upgrade to v3.3, you can safely remove the line-clamp plugin if you were using it:
One thing we’ve found over years and years of designing beautiful stuff with Tailwind is that we literally never set a line-height without also setting the font-size at the same time.
So inspired by our color opacity modifier syntax, we decided to make it possible to save a few characters by setting them together with a single utility:
index.html
<pclass="text-lg leading-7 ..."><pclass="text-lg/7 ..."> So I started to walk into the water. I won't lie to you boys, I was terrified. But
I pressed on, and as I made my way past the breakers a strange calm came over me.
I don't know if it was divine intervention or the kinship of all living things but
I tell you Jerry at that moment, I <em>was</em> a marine biologist.</p>
You can use any value defined in your line-height scale, or use arbitrary values if you need to deviate from your design tokens:
When using custom fonts, you’ll often want to configure things like font-feature-settings or font-variation-settings to opt-in to specific tweaks the font offers.
We’ve made it easy to do this for font-feature-settings for a while, but now you can do the same thing with font-variation-settings by providing a value for it in the sort-of options object you can plop after the font list in your config file:
tailwind.config.js
module.exports={theme:{fontFamily:{sans:['Inter var, sans-serif',{fontFeatureSettings:'"cv11", "ss01"',fontVariationSettings:'"opsz" 32',},],},},}
In the example above we’re using a recent release of Inter that supports using the optical size axis to trigger the “Display” variation of the font, optimized for larger sizes like headlines.
We’re not going to start shipping vegetable clip art with the framework, but you can use any image you want either as an arbitrary value or configuring it in the listStyleImage section of your theme.
Ever heard of the ­ HTML entity? Me neither until we added support for these hyphens-* utilities.
Using hyphens-manual and a carefully placed ­, you can tell the browser where to insert a hyphen when it needs to break a word across multiple lines:
Officially recognized by the Duden dictionary as the longest word in German, Kraftfahrzeughaftpflichtversicherung is a 36 letter word for motor vehicle liability insurance.
<pclass="hyphens-manual ...">
... Kraftfahrzeug­haftpflichtversicherung is a ...
</p>
Maybe a code snippet like this would be useful to include as part of your unpronounceable death metal band’s press kit so the journalists don’t screw up the hyphenation in the article that finally breaks you on to the scene.
Another new one for me — the <caption> element! We’ve got new caption-* utilities you can use on table captions to control whether they appear at the top or bottom of the table they’re attached to.
Table 3.1: Professional wrestlers and their signature moves.
Wrestler
Signature Move(s)
"Stone Cold" Steve Austin
Stone Cold Stunner, Lou Thesz Press
Bret "The Hitman" Hart
The Sharpshooter
Razor Ramon
Razor's Edge, Fallaway Slam
<table><captionclass="caption-bottom">
Table 3.1: Professional wrestlers and their signature moves.
</caption><thead><tr><th>Wrestler</th><th>Signature Move(s)</th></tr></thead><tbody><tr><td>"Stone Cold" Steve Austin</td><td>Stone Cold Stunner, Lou Thesz Press</td></tr><tr><td>Bret "The Hitman" Hart</td><td>The Sharpshooter</td></tr><tr><td>Razor Ramon</td><td>Razor's Edge, Fallaway Slam</td></tr></tbody></table>
So that’s Tailwind CSS v3.3! No breaking changes, just a bunch of fun new stuff. Give it a try in your projects today by updating to the latest version with npm:
npminstall-D tailwindcss@latest
Yep, another release without text-shadow utilities. Remember that episode of Seinfeld where Kramer tries to see how far he can drive without stopping for gas? That’s my favorite episode.
It’s been months in the making but I’m excited to finally release our next website template — Protocol, a beautiful starter kit for building amazing API reference websites.
Powered by Next.js and MDX and styled with Tailwind CSS, it’s built exactly the way we’d build our own API reference documentation.
Play with the live demo or download the source if you’ve got a Tailwind UI all-access license — it’s a free update of course for all-access customers.
As usual we had a lot of fun getting carried away with the design, and putting that extra layer of polish on things to make it really delightful to browse the site.
We’ve got sticky code blocks that stay in view as you scroll through the request and response details for that endpoint:
There’s also this beautiful hover effect on the homepage cards — it follows your mouse cursor with this gradient glow that uncovers a subtle background pattern:
My favorite detail though has to be the sidebar navigation, which tracks the visible page content but using a sort of “minimap” strategy, where all visible page sections are highlighted:
Watching this animate as you scroll through the page is really a sight to behold — props to Framer Motion for doing the heavy lifting here as usual. Even if I absolutely hated React I’m pretty sure I’d still use it just to use this library, it’s really that good.
We spent a lot of time deciding how to wire up the actual content on this one. We explored a bunch of different options for autogenerating documentation using different standards, but for my tastes anyways it all felt a little restrictive.
Personally I want to be able to just write exactly the documentation I want. So for Protocol, we optimized for maximum control but with a lot of authoring conveniences that make it really easy to write exactly what you want, fast.
You write your endpoint documentation in MDX, mixing in a handful of little components we provide to structure things quickly:
messages.mdx
## Create a message {{tag:'POST',label:'/v1/messages'}}<Row><Col>
Publishes a new message to a specific conversation.
### Required attributes
<Properties><Propertyname="conversation_id"type="string">
Unique identifier for the conversation the message belongs to.
</Property><Propertyname="message"type="string">
The message content.
</Property></Properties></Col><Colsticky><CodeGrouptitle="Request"tag="POST"label="/v1/messages">
```bash {{ title: 'cURL' }}
curl https://api.protocol.chat/v1/messages \
-H "Authorization: Bearer {token}" \
-d conversation_id="xgQQXg3hrtjh7AvZ" \
-d message="You're what the French call 'les incompetents.'"
```
```js
import ApiClient from '@example/protocol-api'
const client = new ApiClient(token)
await client.messages.create({
conversation_id: 'xgQQXg3hrtjh7AvZ',
message: 'You're what the French call 'les incompetents.'',
})
```
</CodeGroup>
```json {{ title: 'Response' }}
{
"id": "gWqY86BMFRiH5o11",
"conversation_id": "xgQQXg3hrtjh7AvZ",
"message": "You're what the French call 'les incompetents.'",
"reactions": [],
"created_at": 692233200,
}
```
</Col></Row>
This will produce documentation that looks like this:
To really nail the authoring experience, we even built mdx-annotations — a new library that brings the annotations feature we loved when working with Markdoc over to MDX.
It lets you pass props into tags in MDX content by annotating them with an object, like this heading:
## Create a message {{tag:'POST',label:'/v1/messages'}}
…which is translated into this JSX:
<Headinglevel={2}tag="POST"label="/v1/messages">Create a message</Heading>
This lets you move quite a bit faster because you can keep writing in Markdown and not have to drop into raw JSX just to pass along some extra data.
I think this template is going to be really useful to lots of people right off-the-shelf, so it was important to us that it was easy to customize the design to match your brand.
We deliberately designed the illustrated background pattern we use in the site to feel “on brand” for basically anyone — you can tell it’s the work of a professional designer but it’s simple and leans into the “technical” motif, which is something that all API reference sites are going to have in common anyways.
We built the pattern in code rather than exporting it as an asset with all of the colors baked in, so it’s easy to tweak it to match your own color scheme.
For syntax highlighting, we’re using Shiki with the css-variables theme, which makes it easy to update the syntax highlighting for your brand by picking just 9 colors:
This is a hell of a lot less work than trying to craft your own theme from scratch!
In addition to the four icons we’ve used in our demo, we’ve included another 24 icons for a bunch of common API resource types:
Check out this screenshot, where we’ve adapted the Protocol template as if it were being used by our friends at ConvertKit to power their API reference:
Looks a lot different at a quick glance, but when you really dig in there’s actually not much that has changed here at all — just updating some button and link colors, the logo, adjusting the gradient in the illustration, and picking some different syntax highlighting colors.
Naturally the site includes dark mode support — it’s meant for developers, do you really think we could possibly be that ignorant? You would never forgive us.
The dark mode version has lots of its own cool design details too — I love the different primary button treatment for instance.
We love Algolia for documentation search, and we use it for the Tailwind CSS website as well as in our Syntax template.
We’ve wired it up for Protocol as well, but this time using Algolia’s headless autocomplete library so we had total control of the search UI:
The nice thing about this approach is we can use regular old utility classes to style everything instead of writing custom CSS to style an already-styled widget, which just feels a lot more right in a Tailwind CSS project.
And that’s it — one last Tailwind UI template to finish out 2022! We’ve got another one almost ready to go too, so keep an eye out for that in the new year. Going to have some pretty damn exciting Tailwind CSS v4.0 news to share soon too!
Well it’s that time again! The time where we quickly go from “I really have no idea what we could even add to a new Tailwind release” to “wow, well this is actually a ridiculous amount of new stuff — we better tag a release before things get completely out of hand”.
Tailwind CSS v3.2 is here with an absolutely massive amount of new stuff, including support for dynamic breakpoints, multiple config files in a single project, nested groups, parameterized variants, container queries, and more.
As always check out the release notes for every nitty-gritty fix and improvement, but here’s the highlight reel:
This makes it a lot easier to build multiple stylesheets in a single project that have separate Tailwind configurations. For example, you might have one config file for the customer-facing part of your site, and another config for the admin/backend area.
You’ve always technically been able to do this with enough webpack wizardry, but the new @config directive makes it super easy and accessible to everyone, even in projects where you don’t have as much control over the build tool configuration.
You can now conditionally style things based on whether a certain feature is supported in the user’s browser with the supports-[...] variant, which generates @supports rules under the hood.
The supports-[...] variant takes anything you’d use with @supports (...) between the square brackets, like a property/value pair, and even expressions using and and or.
If you only need to check if a property itself is supported, you can even just specify the property name and Tailwind will fill in the blanks for you:
If you need to use a one-off aria modifier that doesn’t make sense to include in your theme, or for more complex ARIA attributes that take specific values, use square brackets to generate a property on the fly using any arbitrary value.
You can now conditionally style things based on data attributes with the new data-* variants.
Since there are no standard data-* attributes by definition, we only support arbitrary values out of the box, for example:
<!-- Will apply --><divdata-size="large"class="data-[size=large]:p-8"><!-- ... --></div><!-- Will not apply --><divdata-size="medium"class="data-[size=large]:p-8"><!-- ... --></div><!-- Generated CSS --><style>.data-\[size\=large\]\:p-8[data-size="large"]{padding:2rem;}</style>
You can configure shortcuts for common data attribute selectors you’re using in your project under the data key in the theme section of your tailwind.config.js file:
These variants also work as group-* and peer-* variants like many other variants in the framework:
<divdata-size="large"class="group"><divclass="group-data-[size=large]:p-8"><!-- Will apply `p-8` --></div></div><divdata-size="medium"class="group"><divclass="group-data-[size=large]:p-8"><!-- Will not apply `p-8` --></div></div>
We’ve added a new max-* variant that lets you apply max-width media queries based on your configured breakpoints:
<divclass="max-lg:p-8"><!-- Will apply `p-8` until the `lg` breakpoint kicks in --></div>
As a general rule I would still recommend using min-width breakpoints personally, but this feature does unlock one useful workflow benefit which is not having to undo some style at a different breakpoint.
For example, without this feature you often end up doing things like this:
It’s important to note that these features will only be available if your project uses a simple screens configuration.
These features are a lot more complicated than they look due to needing to ensure that all of these media queries are sorted in the final CSS in a way that gives you the expected behavior in the browser. So for now, they will only work if your screens configuration is a simple object with string values, like the default configuration:
If you have a complex configuration where you already have max-width breakpoints defined, or range-based media queries, or anything other than just strings, these features won’t be available. We might be able to figure that out in the future but it just creates so many questions about how the CSS should be ordered that we don’t have answers for yet.
So for now (and possibly forever), if you want to use these features, your screens configuration needs to be simple. My hope is that these features make complex screens configurations unnecessary anyways.
It’s now possible to create custom group-* and peer-* variants on the fly by passing your own selector to be “groupified” or “peerified” between square brackets:
<divclass="group is-published"><divclass="hidden group-[.is-published]:block">
Published
</div></div>
For more control, you can use the & character to mark where .group or .peer should end up in the final selector relative to the selector you are passing in:
Let’s be serious you’re probably going to use these features like three times in your entire life but it’s still pretty cool. Hoping we can use this as a building block to make group and peer work more automatically with variants registered by third-party plugins in the future.
You’ve probably noticed this new variant-[...] syntax in a lot of these new features — this is all powered by a new matchVariant plugin API that makes it possible to create what we’re calling “dynamic variants”.
Here’s an example of creating a placement-* variant for some imaginary tooltip library that uses a data-placement attribute to tell you where the tooltip is currently positioned:
let plugin =require("tailwindcss/plugin");
module.exports={// ...plugins:[plugin(function({ matchVariant }){matchVariant("placement",(value)=>{return`&[data-placement=${value}]`;},{values:{t:"top",r:"right",b:"bottom",l:"left",},});}),],};
The variant defined above would give you variants like placement-t and placement-b, but would also support the arbitrary portion in square brackets, so if this imaginary tooltip library had other potential values that you didn’t feel the need to create built-in values for, you could still do stuff like this:
When defining a custom variant with this API, it’s often important that you have some control over which order the CSS is generated in to make sure each class has the right precedence with respect to other values that come from the same variant. To support this, there’s a sort function you can provide when defining your variant:
Sometimes you can run into problems when you have multiple group chunks nested within each other because Tailwind has no real way to disambiguate between them.
To solve this, we’re adding support for variant modifiers, which are a new dynamic chunk that you can add to the end of a variant (inspired by our optional opacity modifier syntax) that you can use to give each group/peer your own identifier.
This lets you give each group a clear name that makes sense for that context on the fly, and Tailwind will generate the necessary CSS to make it work.
I’m really excited to have a solution out there for this because it’s something I’ve been trying to land on a good approach for solving for several years, and this is the first thing we’ve come up with that really feels like it offers the power and flexibility I think it should.
I can barely believe it but container queries are finally real and the browser support is dangerously close to making these ready for production — in fact if you’re building an Electron app you could use these today.
Today we’re releasing @tailwindcss/container-queries which is a new first-party plugin that adds container query support to the framework, using a new @ syntax to differentiate them from normal media queries:
Right now we’re starting with simple min-width based container queries, but we plan to expand the scope over time, and when it feels like we’ve really nailed the APIs we’ll bring it all into core.
For complete documentation, check out the plugin on GitHub.
So there you have it — Tailwind CSS v3.2! Major improvements but just a minor version change, so no breaking changes and you should be able to update your project by just updating your dependency:
npminstall-D tailwindcss@latest
Yeah I hear you in the back, still no text shadows, but hey at least you can style the sibling of a checkbox when the checkbox’s parent is the third child in a list without leaving your HTML. Priorities people.
We just released a stunning new personal website template for Tailwind UI, redesigned Heroicons from scratch, tagged a new version of Headless UI with some exciting new features, and more.
It’s been a busy week wrapping up a bunch of projects we’ve been working on, but everything is finally out the door so it feels like a good time for another update.
We triaged and resolved another 226 GitHub issues and pull requests since my last post, and we’re finally at under 50 open issues/PRs across our entire organization for the first time ever. So please stop finding bugs I just want to design and build beautiful stuff with Tailwind CSS for a while.
Speaking of beautiful stuff, here’s what shipped over the last couple of weeks!
A couple of days ago we released Spotlight, a stunning new personal website template we designed for Tailwind UI.
Like our other templates, it’s built with Next.js, and this time we’re using MDX to power all of the markdown-driven stuff like the blog section.
Designing this template was a pretty fun and interesting challenge — we wanted to come up with something that was really beautiful and inspiring, but that was also unopinionated enough to feel like a good fit for almost anyone. We landed on a pretty minimalist design that gets it’s personality from little touches like rotated images, hints of color for links, and some subtle depth from shadows and layering in places like the top navigation.
As always, I recommend checking out the live preview for the full experience — especially pay attention to the way the avatar and navigation works on the home page as you scroll, it’s very *chef’s kiss* when you’re playing with the real site.
We tried to structure the site the way we’d structure our own personal sites, so it includes a dedicated blog, a page for you to list some of your favorite projects you’ve worked on, an area for you to link to things like conference talks you’ve given, and a “uses” page to list all of your favorite tools and gear.
If you’ve got a Tailwind UI all-access license then you’ve already got access to this template! And if you don’t, consider it — it’s the best way to support our work on open-source projects like Tailwind CSS, Headless UI, and Heroicons.
Last year we released Heroicons v1.0. Well last week we released Heroicons v2.0, which is a brand new icon set, illustrated from scratch that Steve has been working on for about a year.
It includes 280 icons drawn in three distinct styles:
Outline — line icons with a 1.5px stroke, drawn in a 24px view box.
Solid — solid icons with filled shapes, drawn in a 24px view box.
Mini — solid icons with filled shapes, drawn in a 20px view box.
The biggest differences from v1 are that the outline set uses a thinner stroke, which feels a bit more modern and fashionable these days, and visually the icons are a bit more playful in style.
Even though these have “v2” in the name, it’s better to think of Heroicons v2 more like Terminator 2 than OpenSSL 2 — we feel like they represent our best work but it’s a new icon set, not strictly an upgrade from the original icon set. Don’t feel pressured to upgrade existing projects like you would with a real application dependency, but if you want to migrate, check out the release notes for everything you need to switch.
We’ve added a new by prop to the Listbox, Combobox, and RadioGroup components that make it a lot less cumbersome to bind an object as the form value.
The by prop lets you specify which property of an object should be used for comparisons, so that the bound value and the corresponding value in the list of potential values no longer have to be the exact same object instance:
This makes it a lot easier for the value to come from outside the component, and saves you having to just bind the id or similar and do a bunch of lookups yourself to find the full object when needed.
The Listbox, Combobox, and RadioGroup components now let you optionally pass a defaultValue instead of a value, allowing you to use them as an uncontrolled component.
import{Listbox}from'@headlessui/react'const people =[{id:1,name:'Durward Reynolds'},{id:2,name:'Kenton Towne'},{id:3,name:'Therese Wunsch'},{id:4,name:'Benedict Kessler'},{id:5,name:'Katelyn Rohan'},]functionExample(){return(<formaction="/projects/1/assignee"method="post"><Listboxname="assignee"defaultValue={people[0]}><Listbox.Button>{({ value })=> value.name}</Listbox.Button><Listbox.Options>{people.map((person)=>(<Listbox.Optionkey={person.id}value={person}>{person.name}</Listbox.Option>))}</Listbox.Options></Listbox><button>Submit</button></form>)}
This can simplify your code when using traditional HTML forms or form APIs that collect their state using FormData instead of tracking it using React state.
Historically, you’ve always had to style the different states of a Headless UI component by inspecting arguments passed through a render prop and conditionally rendering whatever classes or content made sense. This could feel like a lot of boilerplate when just trying to tweak a background color or make some other CSS-only change.
In Headless UI v1.7, we’ve added a data-headlessui-state attribute to the rendered HTML that includes information about the current state so you can target it with just CSS.
We’ve also released a new @headlessui/tailwindcss plugin that gives you variants for these states so they are super easy to style with just Tailwind CSS classes:
Not everyone knows this but we ship an insiders build of Tailwind CSS to npm that is automatically built and deployed every single time a new commit lands in the repository. This makes it really easy to test out new features and fixes before they are actually tagged in a proper release.
Well now we include access to the insiders build in Tailwind Play as well, so you can play with bleeding edge stuff without even setting up a project:
We only keep the latest insiders build on Play, so if you create a demo using an insiders build know that it might break if the next insiders build changes something in some unreleased feature you were using. You shouldn’t be putting important things there anyways, come on be a professional.
A while ago we started talking with the Phoenix team because they wanted to ship Tailwind CSS by default in a future release. I thought this was super exciting, and wanted to work with them to make the out-of-the-box experience really beautiful.
We designed a new splash screen and all of the necessary scaffolding for their generator system, which will ship as part of Phoenix v1.7.
Chris McCord the creator of Phoenix gave a great talk last week that walks through all of the Tailwind CSS stuff they are shipping, worth a watch if you’re curious to learn more.
So there you go, that’s all of the coolest stuff we’ve been working on over the last few weeks!
Over the next month or so I’m excited to build a bunch of new Tailwind UI components we’ve been designing, explore some new feature ideas for Tailwind CSS, and start doing some R&D on what it would look like to create a sort of application starter kit template with Tailwind + Next.js — think it could be pretty cool if we can nail it.
All about the brand new Tailwind UI template we just shipped, the official Tailwind CSS job board, and a bunch of new projects coming out in the next few weeks.
Got another update for you today on some cool things we’ve shipped, and some other things that are in the works but shipping soon!
About a week ago we shipped a brand new Tailwind UI template, built with Next.js and (of course) Tailwind CSS.
We’re calling it Pocket, and it’s a mobile app landing page loaded with tons of fun animations and interactions, powered by Framer Motion which is basically the coolest library anyone has ever made.
Be sure to check out the live preview for the full experience — screenshots miss all the coolest parts, like the graph that draws in on page load, the animated mobile device UI in the features section, and the Frogger-inspired testimonials animation.
Funny story about this template — when we initially designed it and built it we actually didn’t plan to do anything in terms of animations or interactions. We put together a static design in Figma that we were all really excited about, then built the whole thing out with a plan to ship it like three and a half weeks ago.
But once we had the finished template in the browser it just kinda felt stiff. It looked great in Figma where you sort of expect everything to be still and static, but once we could see it in real life it felt more like a screenshot or something instead of a real, interactive website.
We decided to delay the release for a couple of weeks while we experimented with some things we could do to breathe some life into it, and after trying a bunch of different ideas ended up with what we have today.
I’m super stoked about how this one turned out, and I think the codebase is a really interesting case study to pour over if you want to learn how to use Framer Motion to pull off some of the cool effects we landed on.
And like all of our templates, if you’ve got a Tailwind UI all-access license, it’s included alongside all of our other components and templates at no extra cost.
We’ve tossed around the idea of building an official Tailwind CSS job board for about two years, and finally made the decision in the last few weeks to give it a shot and see what happens.
If you’re a company that uses Tailwind and are looking for front-end engineers or you’re a developer who wants to work at a company that uses Tailwind, check out Tailwind Jobs to learn more about it.
We broke ground on the codebase on July 14th, and launched the site to companies on August 2nd. We built it with Laravel, Inertia, React, and of course Tailwind CSS. Pretty amazing what you can build with tools like these in just 20 days!
We’re still figuring out the positioning and what we can do to make it the best place for companies using Tailwind to find front-end talent, so if you do any hiring at your company and have any feedback or ideas, shoot Peter an email — he’s taking the lead on this project and is looking for people to talk to about it.
One thing we’re trying to figure out for example is how to make it clear that jobs don’t have to be strictly “styling things with Tailwind CSS all day” to be a good fit — there’s really not a lot of jobs like that in the world, we don’t even have any jobs like that here at the company that actually makes Tailwind CSS itself!
But man a lot of people hate wrestling with CSS, even if they’re not writing it every single day.
There’s ton of developers out there who would be ecstatic to know that whenever they do have to do some styling, they’ll get do it with Tailwind instead of some custom CSS spaghetti.
We want the job board to be a place to find React developers, Vue developers, Laravel developers, Rails developers, whatever — as long as the person would get to use Tailwind when it’s time to style something, it’s a good fit for Tailwind Jobs.
We’ve got a bunch of new components designed for Tailwind UI that we’re going to start building next week
So look for another update in a couple weeks with a bunch more stuff! With the big templates/all-access release behind us I’m excited to switch gears from creating brand new products and spend the rest of the year focused on making Tailwind CSS and Headless UI even better, and adding tons of awesome new stuff to Tailwind UI.
When I was early in my programming career, I loved following thoughtbot.
They were always writing interesting programming articles, producing fantastic screencasts, and publishing incredible books.
I could tell they really cared about their craft and it inspired the hell out of me.
But when they launched Upcase, the thing I was most excited about wasn’t the courses or weekly videos. What I was most excited about was the source code for Upcase itself — the actual-in-production Rails codebase that they gave you access to when you subscribed.
I’d learned a ton from blog posts, books, and screencasts, but I still craved the chance to dig into a real-world codebase crafted by people I trust and study how they put all that knowledge together to build a production-ready application.
That experience was transformative for me, and so for a long time I’ve wanted to give people something like it to learn how real websites are built with Tailwind CSS.
How a layout is broken down into components, what sort of APIs those components expose, how the config has been customized — all of the interesting decisions that you can’t see when you just click view source in the browser.
So today we’re releasing our first batch of official Tailwind CSS website templates — beautiful designs engineered into production-quality codebases, powered by Tailwind CSS and Next.js.
We started designing these way back in March and I’m really excited to get these first five out the door so you can check them out and start playing with them:
Syntax — a documentation site template powered by Stripe’s new Markdoc library that’s perfect for things like open-source projects or product documentation.
Primer — a landing page template for ebooks and courses that bakes in a lot of the best practices we’ve learned releasing our own info products.
Salient — a stunning but simple SaaS marketing website you can quickly adapt for your next idea without burning time you wish you were spending on the actual product.
Keynote — a beautiful conference website design that works just as well for small local gatherings as it does for international events.
Transmit — an awesome podcasting template with a custom persistent audio player that you can wire up to your own RSS feed in no time.
We’ve put a ton of time and attention into both the design and the code, and everything is structured exactly the way we’d do it in a real production project. They’re the perfect starting point when you don’t want to start from a blank canvas, and the best way to learn how experts build modern websites with Tailwind CSS.
Each template can be purchased on its own for $99, or you can get all of them with all-access — the new get-everything-we-ever-add-forever package we’re launching today.
When we started working on templates, one of the things I was really sure about is that I wanted it to be easy to get access to all of them.
This was important to me for two reasons:
I want people to think of our templates more like educational resources than traditional website themes. Every template we build has its own unique learning moments, and I don’t want people to have to be selective about what they get to learn from.
I don’t want to worry about ROI on a per-template basis. Nothing would suck the fun out of designing and building templates like having to make sure each one had mass appeal. I want us to have the freedom to make niche, interesting stuff with cool ideas to study, not just templates that can sell enough copies on their own to justify the work we put into them.
When I was a kid I went to this music festival in France where almost a hundred bands performed. There were a couple of bands on the bill I absolutely loved, knew every song, and would buy a ticket to see any day of the week.
Most of them though were bands I really liked, but wouldn’t have made the 90 minute drive to make it out to a show. But putting all of them on a bill together got me to buy a plane ticket to Paris, and have one of the best experiences of my life.
I want Tailwind UI to feel like a music festival, where you can wander from stage to stage, enjoy a song or two from a band you’ve never heard of, and once in a while be blown away by a performance you would have never otherwise bothered to check out.
So we’re launching a new all-access package for Tailwind UI that includes access to every template and component package that exists today and any new content we add in the future.
It’s a one-time purchase of $299. No subscription, no upgrade costs — if we add something new to Tailwind UI, it’s yours, forever.
And if you already own everything in Tailwind UI, we’ve upgraded you to all-access for free.
So there you have it — that’s everything we’ve been working on for the last few months!
We’ve got a bunch of new templates in the works already, and we’re actively working on extracting a lot of the component ideas from these templates into new examples to include in the Marketing, Application UI, and Ecommerce component categories.
That’s one of my favorite things about working on these site templates — building real-world stuff with Tailwind is the best way for us to come up with new component ideas, and to find ways to improve both Tailwind CSS and Headless UI too.
Look forward to lots of new Tailwind UI stuff in the coming months!
It’s been about six months since we released Tailwind CSS v3.0, and even though we’ve been collecting a lot of little improvements in the codebase since then, we just didn’t have that-one-feature yet that makes you say “okay, it’s release-cuttin’ time”.
Then on a random Saturday night a couple of weeks ago, I was talking to Robin in our Discord about coming up with a way to target the html element using :has and a class deeper in the document, and explained how I thought it would look if we added support for arbitrary variants — something I’ve wanted to tackle for over a year:
Twenty minutes later Robin had a working proof of concept (in six lines of code!), and after another hour or so of Jordan performing regex miracles in our class detection engine, arbitrary variants were born and we had our release-worthy feature.
So here it is — Tailwind CSS v3.1! For a complete list of every fix and improvement check out the release notes, but here’s the highlights:
We’re now shipping types for all of our JS APIs you work with when using Tailwind, most notably the tailwind.config.js file. This means you get all sorts of useful IDE support, and makes it a lot easier to make changes to your configuration without referencing the documentation quite as much.
To set it up, just add the type annotation above your config definition:
If you’re a big TypeScript nerd you might enjoy poking around the actual type definitions — lots of interesting stuff going on there to support such a potentially complex object.
If you’re using our CLI tool to compile your CSS, postcss-import is now baked right in so you can organize your custom CSS into multiple files without any additional configuration.
If you’re not using our CLI tool and instead using Tailwind as a PostCSS plugin, you’ll still need to install and configure postcss-import yourself just like you do with autoprefixer, but if you are using our CLI tool this will totally just work now.
This is especially handy if you’re using our standalone CLI and don’t want to install any node dependencies at all.
I don’t think tons of people know about this, but Tailwind exposes a theme() function to your CSS files that lets you grab values from your config file — sort of turning them into variables that you can reuse.
One limitation though was that you couldn’t adjust the alpha channel any colors you grabbed this way. So in v3.1 we’ve added support for using a slash syntax to adjust the opacity, like you can with the modern rgb and hsl CSS color functions:
If you like to define and configure your colors as CSS variables, you probably have some horrible boilerplate like this in your tailwind.config.js file right now:
Instead of writing a function that receives that opacityValue argument, you can just write a string with an <alpha-value> placeholder, and Tailwind will replace that placeholder with the correct alpha value based on the utility.
If you haven’t seen any of this before, check out our updated Using CSS variables documentation for more details.
I know what you’re thinking — “I have never in my life wanted to build a table that looks like that…” — but listen for a second!
One situation where this is actually super useful is when building a table with a sticky header row and you want a persistent bottom border under the headings:
Scroll this table to see the sticky header row in action
You might think you could just use border-collapse here since you actually don’t want any space between the borders but you’d be mistaken. Without border-separate and border-spacing-0, the border will scroll away instead of sticking under the headings. CSS is fun isn’t it?
Notice how when you hover over the button, the background still changes color even though it’s disabled? Before this release, you’d usually fix that like this:
Instead of overriding the hover color back to the default color when the button is disabled, we combine the hover and enabled variants to just not apply the hover styles when the button is disabled in the first place. I think that’s better!
Here’s an example combining the new optional modifier with our sibling state features to hide a little “Required” notice for fields that aren’t required:
This lets you use the same markup for all of your form groups and letting CSS handle all of the conditional rendering for you instead of handling it yourself. Kinda neat!
Did you know there’s a prefers-contrast media query? Well there is, and now Tailwind supports it out of the box.
Use the new contrast-more and contrast-less variants to modify your design when the user has requested more or less contrast, usually through an operating system accessibility preference like “Increase contrast” on macOS.
Try emulating `prefers-contrast: more` in your developer tools to see the changes
<form><labelclass="block"><spanclass="block text-sm font-medium text-slate-700">Social Security Number</span><inputclass="border-slate-200 placeholder-slate-400 contrast-more:border-slate-400contrast-more:placeholder-slate-500"/><pclass="mt-2 opacity-10 contrast-more:opacity-100 text-slate-600 text-sm">
We need this to steal your identity.
</p></label></form>
I wrote some documentation for this but honestly I wrote more here than I did there.
There’s a pretty new HTML <dialog> element with surprisingly decent browser support that is worth playing with if you like to live on the bleeding edge.
Dialogs have this new ::backdrop pseudo-element that’s rendered while the dialog is open, and Tailwind CSS v3.1 adds a new backdrop modifier you can use to style this baby:
This is super useful for variants that sort of feel like they need to be parameterized, for example adding a style if the browser supports a specific CSS feature using a @supports query:
Now should you do this? Probably not very often, but honestly it can be a pretty useful escape hatch when trying to style HTML you can’t directly change. It’s a sharp knife, but the best chefs aren’t preparing food with safety scissors.
Play with them a bit and I’ll bet you find they are a great tool when the situation calls for it. We’re using them in a couple of tricky spots in these new website templates we’re working on and the experience is much nicer than creating a custom class.
So that’s Tailwind CSS v3.1! It’s only a minor version change, so there are no breaking changes and you should be able to update your project by just installing the latest version:
npminstall tailwindcss@latest
For the complete list of changes including bug fixes and a few minor improvements I didn’t talk about here, dig in to the release notes on GitHub.
I’ve already got a bunch of ideas for Tailwind CSS v3.2 (maybe even text shadows finally?!), but right now we’re working hard to push these new website templates over the finish line. Look for another update on that topic in the next week or two!
It’s been a while since I’ve written about what we’ve been working on so I have a lot to share! Too much honestly — my main motivator for even getting this update out is that we’ve got even more stuff coming next week, and I feel like I’m not allowed to share that stuff until I share all of the stuff we’ve already shipped.
So put your swim suit on, sit back in your lounge chair, and prepare to soak up some vitamin CSS.
A few weeks ago we released a new minor version of Headless UI, the unstyled UI library we built to make it possible to add React and Vue support to Tailwind UI.
Check out the release notes for all of the details, but here are some of the highlights.
Prior to v1.6, if you deleted the contents of a combobox and tabbed away, it would restore the previously selected option. This makes sense a lot of the time, but sometimes you really do want to clear the value of a combobox.
We’ve added a new nullable prop that makes this possible — just add the prop and now you can delete the value without the previous value being restored:
Now if you add a name prop to form components like Listbox, Combobox, Switch, and RadioGroup, we’ll automatically create a hidden input that syncs with the component’s value.
This makes it super easy to send that data to the server with a regular form submission, or with something like the <Form> component in Remix.
This works with simple values like numbers and string, but also with objects — we automatically serialize them into multiple fields using that square bracket notation from 1996:
Dialogs are literally the hardest thing to build on the planet. We’ve been wrestling with gnarly scrollingissues for a while now, and think we’ve finally got it all sorted out in v1.6.
The crux of it is that we’ve changed how “click outside to close” works. We used to use this Dialog.Overlay component that you put behind your actual dialog, and we had a click handler on that that would close the dialog on click. I actually really love the simplicity of this in principle — it’s a lot less quirky to detect when a specific element is clicked than it is to detect when anything other than a specific element is clicked, especially when you have things rendered inside your dialog that themselves are rendering other things in portals and stuff.
The problem with this approach is that if you had a long dialog that required scrolling, your overlay would sit on top of your scrollbar, and trying to click the scrollbar would close the dialog. Not what you want!
So to fix this in a non-breaking way, we’ve added a new Dialog.Panel component you can use instead, and now we close the dialog any time you click outside of that component, rather than closing it specifically when the overlay is clicked:
One of the many reasons dialogs are the hardest thing to build on the planet is because of focus trapping. Our first attempt at this involved hijacking the tab key and manually focusing the next/previous element, so that we could circle back to the first item in the focus trap when you get to the end.
This works okay until people start using portals inside the focus trap. Now it’s basically impossible to manage because you could tab to a datepicker or something that is conceptually inside the dialog, but isn’t actually because it’s rendered in a portal for styling reasons.
Robin came up with a really cool solution for this that is super simple — instead of trying to manually control how tabbing works, just throw an invisible focusable element at the beginning of the focus trap and another one at the end. Now whenever one of these sentinel elements receives focus you just move focus to where it actually should be, based on whether you’re at the first element or the last element and whether the user was tabbing forwards or backwards.
With this approach, you don’t have to hijack the tab key at all — you just let the browser do all of the work and only move focus manually when one of your sentinel elements receives focus.
After figuring this out we noticed a couple of other libraries already doing the same thing so it’s nothing groundbreaking or new, but I thought it was pretty damn clever and worth sharing for anyone who hadn’t thought of this technique.
When we first released Tailwind UI, the “team” was just me and Steve, so we had to keep a lot of things simple if we wanted any chance of actually getting the thing out the door with just the two of us working on it.
One of those things was team licensing. We didn’t ship with any fancy team member invitation flow or anything, we just asked people to share their Tailwind UI credentials with their team. This was good enough for us to get things out the door, because Tailwind UI doesn’t really do anything in a user-specific way, and every member of your team gets the same experience anyways.
Plus to us, having to get the email addresses of everyone on your team, enter them into some form, send each person an invitation email, and have them accept the invitation felt like administrative hell, especially when every single person gets the same experience after they sign in.
At the same time though, sharing credentials for anything is pretty low-end, and it’s not a design decision we took a lot of pride in. I use the same password (slayerfan1234) for Tailwind UI as I do for my bank account — I don’t want to share that with anyone!
So a couple of weeks ago we decided to figure it out and build something.
What we landed on was a purely link based invitation system, where you could just copy your invite link, share it with your team in Slack/Discord/whatever, and reset your link if needed. You can also give people either “Member” or “Owner” permissions, which control whether they can manage team members or view billing history.
This makes it super easy to invite your team without a bunch of tedious data entry, and revoke access if someone leaves right in the UI instead of by changing your shared password.
This is available now for anyone with a Tailwind UI team account — just open the dropdown menu and click “My Team” to name your team and start inviting your co-workers.
Since releasing Vue support for Tailwind UI, the new <script setup> syntax in Vue 3 has become the recommended way to write your single-file components.
We’ve updated all of the Vue examples in Tailwind UI to use this new format, which cuts out a ton of boilerplate:
To me the absolute best part is that you don’t have to explicitly register anything under components anymore — any components that are in scope are automatically available to the template.
Using <script setup> also lets you use namespaced components like Listbox.Button like we do in the React flavor of Headless UI. We haven’t updated Headless UI to expose the components this way yet but we’re probably going to do it soon, which will let you shave off a ton of imports.
Tailwind uses a bunch of non-standard at-rules like @tailwind and @apply, so you get lint warnings in VS Code if you use the regular CSS language mode.
To get around this, we’ve always recommended people use the PostCSS Language Support plugin which gets rid of those warnings, but also removes all of the other CSS IntelliSense support.
So a few weeks ago we released a first-party Tailwind CSS language mode as part of our Tailwind CSS IntelliSense extension, which builds on the built-in CSS language mode to add Tailwind-specific syntax highlighting and fix the lint warnings you’d usually see, without losing any of CSS IntelliSense features you do want to keep.
Try it out by downloading the latest version of Tailwind CSS IntelliSense and choosing “Tailwind CSS” as the language mode for your CSS files.
We’ve made a bunch of little improvements to Tailwind Play over the last couple of months, with my favorite being the new “Generated CSS” panel.
It shows you all of the CSS that was generated from your HTML and lets you filter by layer, which is incredibly useful for troubleshooting. Internally we are using this all the time to debug weird issues around classes not being detected so we can perform whatever horrific regex surgery is necessary to make it work.
We also added a “Tidy” button (Cmd + S) to each pane that will automatically format your code (and sort your classes!) and a “Copy” button (Cmd + ACmd + C, but you already know that) too.
When we released Refactoring UI back in December 2018, Steve and I literally designed and built the final landing page the night before launch at like 1am.
What happened is we had this whole sexy landing page designed, then I was writing up the announcement email to send to everyone on our mailing list and we both thought “man the content in this email is great and a lot more compelling than what we have in this landing page design”.
But that content didn’t really fit into what we had designed, so at the eleventh hour we scrapped everything we had designed and whipped together a much simpler page based on the new content. It looked fine but it wasn’t the super beautiful experience we really wanted it to be.
So a few weeks ago we decided to finally design something new.
I’m still extremely proud of this book — probably more so than anything we’ve ever made. It’s got a 4.68 rating on Goodreads with over 1100 ratings and almost 200 reviews, which feels pretty incredible to me for a self-published ebook.
Looking forward to doing a second edition one day with everything we’ve learned since!
We’ve teased this a bit on Twitter, but for the last couple of months we’ve been working really hard on a bunch of full-fledged Tailwind CSS website templates.
Here’s a sneak peek at one of them — a documentation site template built with Next.js and Stripe’s new Markdoc library:
I’m unreasonably excited about getting these out. I’m really proud of Tailwind UI as a product, but one of the limitations of the copy-and-pasteable-code-snippet format is that we don’t get an opportunity to really show you how to componentize things, minimize duplication, and architect things as a complete, production-ready website.
The templates we’re working on now are going to be amazing at filling that gap. On top of just getting beautiful templates to use as a starting point for your own projects, you’ll be able to dig through the code and study exactly how we build websites with Tailwind CSS ourselves.
We haven’t set an exact release date on these yet but we’re hoping to have something out next month. Will share more as we make more progress!
We just released Headless UI v1.5, which includes a brand new Combobox component. Comboboxes are like select controls but with autocomplete/typeahead functionality, and are a great alternative to a regular select when you’re working with large datasets and want to quickly filter for the right option.
Like all other Headless UI components, the combobox abstracts away all of the complex accessibility considerations but leaves the styling completely up to you, giving you total control to design exactly the combobox you want without worrying about things like keyboard navigation or screen reader support.
Here’s a quick demo if you’d like to see it in action:
Wade Cooper
Arlene McCoy
Devon Webb
Tom Cook
Tanya Fox
Hellen Schmidt
We’ve intentionally designed it so that you have full control over filtering the actual results. You can do basic string comparisons, use a fuzzy search library like Fuse.js, or even make server-side requests to an API — whatever makes sense for your project.
Here’s what it looks like to filter the results using a basic string comparison:
Comboboxes are not only great as standalone inputs, but they can also be used as a lower-level primitive for building more complex components, such as command palettes.
This is actually what originally motivated us to create the combobox component in the first place — we wanted to add a new command palettes category to Tailwind UI and needed this component to make that happen.
If you happen to have a Tailwind UI license, be sure to browse the new Command Palettes category to see how these turned out. And if you’re wondering, we also added a new Comboboxes category as well.
Riding on the excitement of the new command palettes, we also just published a new in-depth screencast on building a command palette from scratch with Tailwind CSS, React and Headless UI.
It covers tons of interesting Tailwind tricks for getting the design and animations just right, and teaches you a ton about how to use the new combobox component and wire it into your app.
If you already have Headless UI installed in your project, be sure to upgrade to v1.5 to get the new Combobox component. This is a minor update so there are no breaking changes.
# For Reactnpminstall @headlessui/react
# For Vuenpminstall @headlessui/vue
People have been talking about the best way to sort your utility classes in Tailwind projects for at least four years. Today we’re excited to announce that you can finally stop worrying about it with the release of our official Prettier plugin for Tailwind CSS.
This plugin scans your templates for class attributes containing Tailwind CSS classes, and then sorts those classes automatically following our recommended class order.
HTML
<!-- Before --><buttonclass="text-white px-4 sm:px-8 py-2 sm:py-3 bg-sky-700 hover:bg-sky-800">...</button><!-- After --><buttonclass="bg-sky-700 px-4 py-2 text-white hover:bg-sky-800 sm:px-8 sm:py-3">...</button>
It works seamlessly with custom Tailwind configurations, and because it’s just a Prettier plugin, it works anywhere Prettier works — including every popular editor and IDE, and of course on the command line.
To get started, install prettier-plugin-tailwindcss as a dev-dependency:
At its core, all this plugin does is organize your classes in the same order that Tailwind orders them in your CSS.
This means that any classes in the base layer will be sorted first, followed by classes in the components layer, and then finally classes in the utilities layer.
<!-- `container` is a component so it comes first --><divclass="container mx-auto px-6"><!-- ... --></div>
Utilities themselves are sorted in the same order we sort them in the CSS as well, which means that any classes that override other classes always appear later in the class list:
The actual order of the different utilities is loosely based on the box model, and tries to put high impact classes that affect the layout at the beginning and decorative classes at the end, while also trying to keep related utilities together:
Responsive modifiers like md: and lg: are grouped together at the end in the same order they’re configured in your theme — which is smallest to largest by default:
Any custom classes that don’t come from Tailwind plugins (like classes for targeting a third-party library) are always sorted to the front, so it’s easy to see when an element is using them:
We think Prettier gets it right when it comes to being opinionated and offering little in terms of customizability — at the end of the day the biggest benefit to sorting your classes is that it’s just one less thing to argue with your team about.
We’ve tried really hard to come up with a sort order that is easy to understand and communicates the most important information as fast as possible.
The plugin will respect your tailwind.config.js file and work with any Tailwind plugins you’ve installed, but there is no way to change the sort order. Just like with Prettier, we think that the benefits of auto-formatting will quickly outweigh any stylistic preferences you have and that you’ll get used to it pretty fast.
Today we’re announcing the next version of the Tailwind CSS Typography plugin, which brings easy dark mode support, a brand new customization API, and the not-prose class I wasn’t sure we’d ever figure out how to support.
Today we’re announcing the next version of the Tailwind CSS Typography plugin, which brings easy dark mode support, a brand new customization API, and the not-prose class I wasn’t sure we’d ever figure out how to support.
Tailwind CSS Typography v0.5 is designed for Tailwind CSS v3.0, so make sure you’re on the latest version of Tailwind, then install the new plugin release from npm:
npminstall-D @tailwindcss/typography@latest
To learn more about everything the plugin provides, check out our update typography plugin documentation.
Tailwind CSS v3.0 ships with five different sets of grays by default, and the updated typography plugin includes classes for each one, making it easy to match your typography to the rest of your site:
Ever needed to stick some non-content HTML in the middle of your content? Now you can wrap that with not-prose to make sure the prose styles don’t interfere with it:
<articleclass="prose"><h1>My Heading</h1><p>...</p><divclass="not-prose"><!-- Some HTML that needs to be prose-free --></div><p>...</p><!-- ... --></article>
Tailwind CSS is written in JavaScript and distributed as an npm package, which means you’ve always had to have Node.js and npm installed to use it.
Today we’re announcing a new standalone CLI build that gives you the full power of Tailwind CLI in a self-contained executable — no Node.js or npm required.
Tailwind CSS is written in JavaScript and distributed as an npm package, which means you’ve always had to have Node.js and npm installed to use it.
This has made it harder to integrate into projects where using npm isn’t always common, and with tools like Rails and Phoenix both moving away from npm by default, we needed to find a way for people to use Tailwind in these projects without forcing them to adopt an entirely separate ecosystem of tooling.
Today we’re announcing a new standalone CLI build that gives you the full power of Tailwind CLI in a self-contained executable — no Node.js or npm required.
To install it, grab the executable for your platform from the latest release on GitHub, making sure to give it executable permissions:
Terminal
# Example for macOS arm64curl-sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
chmod +x tailwindcss-macos-arm64
mv tailwindcss-macos-arm64 tailwindcss
Now you can use it just like our npm-distributed CLI tool:
Terminal
# Create a tailwind.config.js file
./tailwindcss init
# Start a watcher
./tailwindcss -i input.css -o output.css --watch# Compile and minify your CSS for production
./tailwindcss -i input.css -o output.css --minify
We’ve even bundled the latest versions of all of our first-party plugins, so if you want to use them in your project, just require them in your tailwind.config.js file like you would in a Node-based project:
We didn’t rewrite Tailwind in Rust or anything (yet…) — we’re actually using pkg, a really cool project by Vercel that lets you turn a Node.js project into an executable that can be run without installing Node.js by bundling all of the parts your project needs right into the executable itself.
This is what makes it possible for you to still use a tailwind.config.js file with the full power of JavaScript, rather than a static format like JSON.
If you are already using npm in your project, use the npm-distributed version of our CLI that we’ve always provided. It’s simpler to update, the file size is smaller, and you’re already in the ecosystem anyways — there’s no benefit at all to using the standalone build.
If on the other hand you’re working on a project where you don’t otherwise need Node.js or npm, the standalone build can be a great choice. If Tailwind was the only reason you had a package.json file, this is probably going to feel like a nicer solution.
…or head over to Tailwind Play to try out the latest features right in the browser.
Tailwind CSS v3.0 is a new major version of the framework and there are some minor breaking changes, but we’ve worked really hard to make the upgrade process as smooth as possible, and for most projects you should be able to install v3.0 without making any changes.
For example, Tailwind UI is probably the biggest Tailwind project on earth and every template is totally compatible with both v2 and v3 with no changes required.
For more details and step-by-step instructions on migrating to v3.0, check out the upgrade guide.
Back in March we introduced the brand new Just-in-Time engine which brought huge performance gains, unlocked exciting new features like arbitrary values, and made complex variant configurations a thing of the past.
In Tailwind CSS v3.0, the new engine has gone stable and replaced the classic engine, so every Tailwind project can benefit from these improvements out of the box.
Before the new engine, we always had to be careful with CSS file size in development, and one of the biggest trade-offs we had to make was carefully limiting the color palette.
In v3.0, every single color in the extended color palette is enabled by default, including lime, cyan, sky, fuchsia, rose, and fifty shades of gray.
People have been asking us for colored shadows for years, but supporting it in a composable way that actually made sense was way harder than I expected it to be.
After about five false starts, we finally figured out an approach we liked, and now Tailwind CSS v3.0 includes colored shadows:
We’ve added a comprehensive set of utilities for the CSS Scroll Snap module, giving you the power to build very rich scroll snapping experiences directly in your HTML:
Scroll in the grid of images to see the expected behaviour
We’ve added support for columns — the newspaper layout kind. These are actually super useful, and are great for things like footer navigation layouts too.
Expedita quo ea quod laborum ullam ipsum enim. Deleniti commodi et. Nam id laborum placeat natus eum. Aliquid aut aut soluta nesciunt culpa magni. Velit possimus autem et aut repudiandae culpa rerum. Qui blanditiis ut qui quia expedita necessitatibus sed. Autem sed ut saepe doloremque aut placeat voluptas ipsum.
Eligendi error nisi recusandae velit numquam nihil aperiam enim. Eum et molestias. Id qui cum veritatis id ea quidem ea rerum saepe. Iste itaque fugiat sequi. Voluptatem quae minus. Maxime ullam ea praesentium recusandae vero est quas. Quia minima fugiat aut laborum impedit facere autem sit qui. Et eos et ullam necessitatibus. Ut voluptatem saepe natus itaque maiores sit repellat aut natus assumenda.
Blanditiis ipsa officia dolores exercitationem nemo beatae voluptatem eos rerum velit asperiores. Non quisquam accusantium officia nisi eius necessitatibus.
Quaerat quia ad voluptatem laudantium natus. Aut ipsa et numquam delectus aliquam. Recusandae libero consequatur dolorum. Animi culpa rerum molestiae ut non et molestias aliquid aut nemo. Sint dolorem dolorem. Iure dolorum amet ea sit perferendis.
Et illum ut officia nisi commodi. Quia et mollitia possimus modi. Delectus aliquid quam eos consectetur.
Accusantium et et qui non sed modi. Corrupti deserunt culpa eos vitae neque aperiam. Repellat tenetur fugit.
Deleniti distinctio ad corrupti nisi. Mollitia qui est natus cumque. Officia dolor qui perferendis necessitatibus saepe excepturi asperiores quos voluptas. Est suscipit facere nihil expedita suscipit quibusdam. Quod cupiditate vero distinctio. Sed est soluta nostrum magnam et saepe blanditiis aut. Vero dolores repellendus et libero minima explicabo provident. Culpa aut dolorem est.
We’ve added support for the new accent-color property, as well as a modifier for styling file input buttons to make it easier than ever to put your own touch on native form controls:
<form><divclass="flex items-center space-x-6"><divclass="shrink-0"><imgclass="h-16 w-16 object-cover rounded-full"src="https://images.unsplash.com/photo-1580489944761-15a19d654956?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1361&q=80"alt="Current profile photo"/></div><labelclass="block"><spanclass="sr-only">Choose profile photo</span><inputtype="file"class="block w-full text-sm text-slate-500
file:mr-4file:py-2file:px-4file:rounded-fullfile:border-0file:text-smfile:font-semiboldfile:bg-violet-50file:text-violet-700hover:file:bg-violet-100"/></label></div><labelclass="mt-6 flex items-center justify-center space-x-2 text-sm font-medium text-slate-600"><inputtype="checkbox"class="accent-violet-500"checked/><span>Yes, send me all your stupid updates</span></label></form>
The new print modifier lets you style how your site should look when animals people print it:
<div><articleclass="print:hidden"><h1>My Secret Pizza Recipe</h1><p>This recipe is a secret, and must not be shared with anyone</p><!-- ... --></article><divclass="hidden print:block">
Are you seriously trying to print this? It's secret!
</div></div>
I’d say “learn more in the print styles documentation” but it wouldn’t be true.
<p>
I’m Derek, an astro-engineer based in Tatooine. I like to build X-Wings at
<ahref="#"class="underline decoration-sky-500 decoration-2">My Company, Inc</a>. Outside of work, I
like to <ahref="#"class="underline decoration-pink-500 decoration-dotted decoration-2">watch pod-racing</a>
and have <ahref="#"class="underline decoration-indigo-500 decoration-wavy decoration-2">light-saber</a>
fights.
</p>
Use the new portrait and landscape modifiers to conditionally add styles when the viewport is in a specific orientation:
<div><divclass="portrait:hidden"><!-- ... --></div><divclass="landscape:hidden"><p>
This experience is designed to be viewed in landscape. Please rotate your
device to view the site.
</p></div></div>
Add that script tag to any HTML document and you can use every Tailwind feature, right in the browser. It’s meant for development purposes only, but it sure is a fun way to build little demos or hack on a new idea.
Almost 6 months in the making, we finally released Tailwind UI Ecommerce — the first all-new component kit for Tailwind UI since the initial launch back in February 2020.
Almost 6 months in the making, we finally released the first all-new component kit for Tailwind UI since the initial launch back in February 2020!
Tailwind UI Ecommerce adds over 100 new components across 14 new component categories and 7 new page example categories, including stuff like:
Product Overviews
Product Lists
Category Previews
Shopping Carts
Category Filters
Product Quickviews
Store Navigation
Promo Sections
Checkout Forms
Customer Reviews
Order Summaries
Storefront Pages
Product Pages
Order History Pages
…and more.
For a quick preview, check out this product page example we shared via our newsletter last week:
It’s been a really fun and challenging process putting this together, and I’m really proud of how it turned out. I wrote up a big post about “Designing Tailwind UI Ecommerce” that’s worth a read if you’re interested in the process behind putting together a new Tailwind UI product like this.
You can check out a bunch more interactive previews as well as screenshots of every single new example over at the Tailwind UI website.
If you like what you see, consider purchasing a license — it’s the best way to support our work on Tailwind CSS and Headless UI and makes it possible for us to keep making these tools better and better.
We just released Headless UI v1.4, which includes a brand new Tab component, and new APIs for manually closing Popover and Disclosure components more easily.
Earlier this year we started working on Tailwind UI Ecommerce, and we realized pretty quickly we were going to need to support tabs in Headless UI to be able to build the new interfaces we were designing.
Like all Headless UI components, this totally abstracts away stuff like keyboard navigation for you so you can create custom tabs in a completely declarative way, without having to think about any of the tricky accessibility details.
Up until now, there was no way to close a Disclosure without clicking the actual button used to open it. For typical disclosure use cases this isn’t a big deal, but it often makes sense to use disclosures for things like mobile navigation, where you want to close it when someone clicks a link inside of it.
Now you can use Disclosure.Button or (DisclosureButton in Vue) within your disclosure panel to close the panel, making it easy to wrap up things like links or other buttons so the panel doesn’t stay open:
import{Disclosure}from'@headlessui/react'importMyLinkfrom'./MyLink'functionMyDisclosure(){return(<Disclosure><Disclosure.Button>Open mobile menu</Disclosure.Button><Disclosure.Panel><Disclosure.Buttonas={MyLink}href="/home">
Home
</Disclosure.Button>{/* ... */}</Disclosure.Panel></Disclosure>)}
The same thing works with Popover components, too:
If you need finer control, we also pass a close function via the render prop/scoped slot, so you can imperatively close the panel when you need to:
import{Popover}from'@headlessui/react'functionMyPopover(){return(<Popover><Popover.Button>Terms</Popover.Button><Popover.Panel>{({ close })=>(<buttononClick={async()=>{awaitfetch('/accept-terms',{method:'POST'})close()}}>
Read and accept
</button>)}</Popover.Panel></Popover>)}
For more details, check out the updated Popover and Disclosure documentation.
Well I can’t say we were really planning on it but over the last few weeks we’ve been having a ton of fun dumping new and exciting features into Tailwind and now feels like the right time to cut a release, so here it is — Tailwind CSS v2.2!
We’ve built-in a new high-performance CLI tool, added ::before and ::after support, introduced new peer-* variants for sibling styling, added variants for styling highlighted text, and tons more.
Well I can’t say we were really planning on it but over the last few weeks we’ve been having a ton of fun dumping new and exciting features into Tailwind and now feels like the right time to cut a release, so here it is — Tailwind CSS v2.2!
This has to be one of the most feature-rich Tailwind releases of all-time. Introducing Just-in-Time mode back in v2.1 has opened the doors to a lot of cool features we couldn’t have easily added otherwise, and this release is loaded with great examples of that.
It’s important to note that although this is a minor release and there are no breaking changes in the classic engine, Just-in-Time mode is still in preview and v2.2 introduces a few very small changes that might impact you, so make sure you read through the changes and deprecations in the release notes when upgrading.
When you’re ready to upgrade, just install the latest version from npm and you’re off to the races:
No installation or configuration necessary — simply npx tailwindcss -o output.css to compile Tailwind from anywhere. You can even enable JIT mode with the --jit flag and pass in your content files using the --purge option, all without creating a config file.
Watch mode — so you can automatically rebuild your CSS whenever you make any changes.
JIT performance optimizations — since our CLI is Tailwind-specific we’ve been able to make tons of optimizations that make it the absolute fastest build tool for compiling your CSS in JIT mode.
Minification support — now you can minify your CSS with cssnano just by adding the --minify flag.
PostCSS plugin support — the new CLI will read and respect any extra plugins you configure using a postcss.config.js file.
It’s fully backwards-compatible with the previous CLI, so if you’ve got any scripts set up already you should be able to upgrade to v2.2 without making any changes to your scripts.
Note that if you were using the tailwindcss-cli wrapper package, you can safely switch to tailwindcss as we’ve managed to resolve the peer-dependency issues that forced us to create the wrapper package in the first place.
We set content: "" automatically any time you use a before or after variant to make sure the elements are rendered, but you can override it using the new content utilities which have full arbitrary value support:
We’ve added variants for the first-letter and first-line pseudo-elements, so you can do stuff like drop caps:
<pclass="first-letter:text-4xl first-letter:font-bold first-letter:float-left">
The night was March 31, 1996, and it was finally time for Bret Hart to face
off against Shawn Michaels in the long anticipated Iron Man match — a 60
minute war of endurance where the man who scored the most number of falls
would walk away as the WWF World Heavyweight Champion.
</p>
We’ve added a new selection variant that makes it super easy to style highlighted to match your design:
<pclass="selection:bg-pink-200">
After nearly a grueling hour of warfare with neither man scoring a fall, Hart
locked in the Sharpshooter, his signature submission hold. As Michaels
screamed in pain, the crowd were certain that Hart was about to walk away from
WrestleMania XII as the still-World Heavyweight Champion.
</p>
We’ve even built this feature in such a way that it can be applied to a parent element and cascade down, so you can set a highlight color for your whole site by applying a utility to the body:
<bodyclass="selection:bg-pink-200"><!-- ... --><p>
But Michaels didn't give up — he held on until the bell rang and the
designated 60 minutes was up. Hart walked away content, thinking that
without a clear winner, the title was his to hold. He was not prepared for
what would happen next, when Gorilla Monsoon declared the match would
continue under sudden death rules.
</p></body>
Just like group can be combined with any other variant, peer can as well, so you have variants like peer-hover, peer-focus, peer-disabled, and loads more at your fingertips.
We’ve added variants for basically every single missing pseudo-class we could think of in this release:
only(only-child)
first-of-type
last-of-type
only-of-type
target
default
indeterminate
placeholder-shown
autofill
required
valid
invalid
in-range
out-of-range
Personal favorite in the list is placeholder-shown — when combined with the new sibling selector variants it makes it possible to do cool stuff like floating labels:
Instead of using utilities like bg-opacity-50, text-opacity-25, or placeholder-opacity-40, Tailwind CSS v2.2 gives you a new color opacity shorthand you can use to tweak the alpha channel of a color directly in the color utility itself:
This means you can now change the opacity of colors anywhere in Tailwind, even where we previously didn’t have specific opacity utilities, like in gradients for example:
The opacity values are taken from your opacity scale, but you can also use arbitrary opacity values using square bracket notation:
<divclass="bg-red-500/[0.31]"></div>
If I’m being honest, I am more excited about never having to create another core plugin like placeholderOpacity.js for you people again than I am about actually using the feature. And I’m really excited about the feature, so that says something.
We’ve gone over every core plugin in Tailwind to try and add the most flexible arbitrary value support we possibly could, and I think we’ve covered pretty much everything at this point.
You should be able to whatever arbitrary values you want, just about wherever you want:
If you find one we missed, open an issue and we’ll sort it out.
In addition to making arbitrary value support more comprehensive, we’ve also added a new type-hint syntax to handle ambiguous situations. For example, if you are using a CSS variable as an arbitrary value, it’s not always clear what the generated CSS should be:
<!-- Is this a font size utility, or a text color utility? --><divclass="text-[var(--mystery-var)]"></div>
Now you can provide a hint to the engine by prefixing the arbitrary value with the type name:
Since Tailwind introduces a lot of non-standard CSS at-rules like @tailwind and @apply, you can often run into weird output when combining it with a PostCSS nesting plugin like postcss-nested or postcss-nesting.
To ease the pain here, we’ve included a new PostCSS plugin in the tailwindcss package that acts as a lightweight compatibility layer between existing nesting plugins and Tailwind itself.
So if you need nesting support in your project, use our plugin, and stick it before Tailwind in your PostCSS plugin list:
By default, it uses postcss-nested under the hood (since that’s what we use to support nesting in Tailwind plugins), but if you’d like to use postcss-nesting instead, just call our plugin as a function and pass through the postcss-nesting plugin:
Under the hood, this uses a new screen() function we’ve introduced that you can use to get the expanded media expression from any of your configured breakpoints:
You probably won’t need to use this yourself but it could be helpful if you’re ever integrating Tailwind with another tool that understands @media but doesn’t handle @screen properly.
We’ve added new utilities for the background-origin property, which let you control where an element’s background is positioned relative to the element’s border, padding box, or content:
<divclass="bg-origin-border p-4 border-4 border-dashed ..."style="background-image:url(...)">
Background is rendered under the border
</div><divclass="bg-origin-padding p-4 border-4 border-dashed ..."style="background-image:url(...)">
Background is rendered within the border but on top of any padding
</div><divclass="bg-origin-content p-4 border-4 border-dashed ..."style="background-image:url(...)">
Background is rendered within any padding and under the content
</div>
Now those features are automatically enabled any time you use any of the relevant sub-utilities.
It’s important to understand though that because these utilities aren’t needed anymore, you can no longer expect transforms and filters to be “dormant” by default. If you were relying on conditionally “activating” transforms or filters by toggling these classes, you will want to make sure you are toggling the sub-utilities themselves instead:
I don’t expect this will be a real problem for most people, but it’s technically a breaking change which is why we’ve limited this improvement to the JIT engine only.
Requested at least once a month for the last four years, I’m excited to share that we’ve finally added per-side border color support now that we don’t have to sweat the development stylesheet size.
We’ve added first-class support for a bunch of important PurgeCSS features and made them work in the JIT engine as well, which doesn’t actually even use PurgeCSS.
First is safelist, which is super useful if you need to protect specific classes from being removed from your production CSS, perhaps because they are used in content that comes from a database or similar:
Note that while the classic engine will accept regular expressions here, the JIT engine will not. That’s because when we’re generating classes on demand, the class doesn’t exist until it’s used so we have nothing to match the expression against. So if you’re using just-in-time mode, make sure you’re providing complete class names to get the expected result.
Next is transform, which lets you transform content for different file extensions before scanning it for potential class names:
tailwind.config.js
let remark =require('remark')
module.exports={purge:{content:['./src/**/*.{html,md}'],transform:{md:(content)=>{returnremark().process(content)},},},// ...}
This is really useful if you have templates that are written in a language that compiles to HTML, like Markdown.
Finally we have extract, which lets you customize the logic that Tailwind uses to detect class names in specific file types:
Last year we released Tailwind UI — a huge directory of professionally designed UI examples built with Tailwind CSS. Up until now, all of the examples in Tailwind UI have been pure HTML which is sort of the lowest common denominator for all web developers, and makes it possible to adapt them to any templating language or JavaScript framework.
Today we’re excited to add first class support for React and Vue 3 to all of the examples in Tailwind UI, which makes it even easier to adapt them for your projects.
It’s been a long journey but I am super proud of where we ended up on this one, and really think it’s going to make Tailwind UI a useful tool for a whole new group of Tailwind CSS users.
All of the React and Vue examples in Tailwind UI are powered Headless UI which is a library of components we developed to decouple all of the complicated JS behavior you need to build complex components like modals and dropdowns from the actual styles and markup.
Headless UI handles all of the ARIA attribute management, keyboard interactions, focus handling, and more for you, meaning all of the React and Vue examples provided in Tailwind UI are fully functional, with no need to write any of that complex JS stuff yourself. All of that gnarly complexity is safely tucked away in your node_modules folder where we can make improvements and fix bugs on your behalf, without you ever having to change your own code.
With Headless UI, we’ve managed to abstract away all of the complicated JS functionality without taking away any control over the actual markup. That means that the entire design is still in entirely under your control.
You can copy a React or Vue example from Tailwind UI and change absolutely everything about it, from the border radius to the padding to the box shadows to the font-size, all by simply adding utility classes like you’re used to.
If you’re already a Tailwind UI customer, all of this stuff is available to you today as a totally free update. Just log in to your account, select between HTML, React, or Vue in the dropdown above any component, and grab the code in the format you want.
If you haven’t checked out Tailwind UI yet, browse the free preview components to get a feel for how it all works. It’s an awesome tool for moving fast on a new side-project idea, finding inspiration for a new feature you need to build at work, or learning how to implement a specific little UI trick with Tailwind, and a great way to support our work on open-source projects like Tailwind CSS, Headless UI, and Heroicons.
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'functionMyDialog(){let[isOpen, setIsOpen]=useState(true)return(<Dialogopen={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><buttononClick={()=>setIsOpen(false)}>Deactivate</button><buttononClick={()=>setIsOpen(false)}>Cancel</button></Dialog>)}
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'exportdefault{components:{Disclosure,DisclosureButton,DisclosurePanel},}</script>
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.
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.
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.
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.
The first new feature update since Tailwind CSS v2.0 is here and loaded with lots of cool stuff! We’ve merged the new JIT engine to core, added first-class support for composable CSS filters, added blending mode utilities, and a bunch more.
The brand-new JIT engine we announced in March has now been merged into core, and is available as an opt-in feature using a new mode option in your tailwind.config.js file:
tailwind.config.js
module.exports={mode:'jit',purge:[// ...],// ...}
This feature is still in preview which means some details may change as we iron out the kinks, and it’s not subject to semantic versioning.
If you were using @tailwindcss/jit before, you can now migrate to Tailwind CSS v2.1 instead, as that’s where all new development on the engine will happen.
This is a huge one — we’ve finally added first-class support for CSS filters!
They work a lot like our transform utilities, where you use filter to enable filters, and combine it with utilities like grayscale, blur-lg, or saturate-200 to compose filters on the fly.
Just over a year ago we released the very first version of Heroicons, which is a set of beautiful UI icons we designed alongside Tailwind UI. Since then we’ve added tons of new icons, and designed and launched a dedicated web experience.
Today we’re excited to finally release Heroicons v1.0, which includes over 450+ free icons in two styles, official React and Vue libraries, and Figma assets.
In addition to grabbing the icons you need directly from the website, you can now install our official React and Vue libraries for quick and easy access to each icon as a dedicated component.
We’ve also published an official Heroicons Figma file on our new Figma Community page!
It includes all the icons from Heroicons as individual Figma components so you can easily use them in your projects without having to manually import each SVG.
One of the hardest constraints we’ve had to deal with as we’ve improved Tailwind CSS over the years is the generated file size in development. With enough customizations to your config file, the generated CSS can reach 10mb or more, and there’s only so much CSS that build tools and even the browser itself will comfortably tolerate.
Update: As of Tailwind CSS v2.1, the new Just-in-Time engine is included right in Tailwind CSS itself, so you don’t need the @tailwindcss/jit package anymore. Learn more in the documentation.
One of the hardest constraints we’ve had to deal with as we’ve improved Tailwind CSS over the years is the generated file size in development. With enough customizations to your config file, the generated CSS can reach 10mb or more, and there’s only so much CSS that build tools and even the browser itself will comfortably tolerate.
For that reason, you’ve always had to be careful about expensive changes to your config file like adding too many extra breakpoints or enabling extra variants like disabled or focus-visible.
Today I’m super excited to share a new project we’ve been working on that makes these considerations a thing of the past: a just-in-time compiler for Tailwind CSS.
@tailwindcss/jit is a new experimental library that compiles all of your CSS on-demand as you author your template files, instead of generating your entire stylesheet up front.
This comes with a lot of advantages:
Lightning fast build times. Tailwind can take 3–8s to initially compile using our CLI, and upwards of 30–45s in webpack projects because webpack struggles with large CSS files. This library can compile even the biggest projects in about 800ms (with incremental rebuilds as fast as 3ms), no matter what build tool you’re using.
Every variant is enabled out of the box. Variants like focus-visible, active, disabled, and others are not normally enabled by default due to file-size considerations. Since this library generates styles on demand, you can use any variant you want, whenever you want. You can even stack them like sm:hover:active:disabled:opacity-75. Never configure your variants again.
Generate arbitrary styles without writing custom CSS. Ever needed some ultra-specific value that wasn’t part of your design system, like top: -113px for a quirky background image? Since styles are generated on demand, you can just generate a utility for this as needed using square bracket notation like top-[-113px]. Works with variants too, like md:top-[-113px].
Your CSS is identical in development and production. Since styles are generated as they are needed, you don’t need to purge unused styles for production, which means you see the exact same CSS in all environments. Never worry about accidentally purging an important style in production again.
Better browser performance in development. Since development builds are as small as production builds, the browser doesn’t have to parse and manage multiple megabytes of pre-generated CSS. In projects with heavily extended configurations this makes dev tools a lot more responsive.
Try it today by installing @tailwindcss/jit and swapping it into your PostCSS configuration:
We’re shipping it as a separate library for now, but once we’ve worked out all the kinks we’re going to roll it right back into tailwindcss behind a configuration option, and we’re aiming to make it the default in Tailwind CSS v3.0 later this year.
Many years ago I got a message from Steve that said something like “Have I ever shared this guy’s Dribbble profile with you before? Been following him forever, some of my absolute favorite work I’ve ever found”. That person was James McDonald, and today we’re totally over the moon to share that James is joining our team full-time.
Many years ago I got a message from Steve that said something like:
Have I ever shared this guy’s Dribbble profile with you before? Been following him forever, some of my absolute favorite work I’ve ever found.
That person was James McDonald, and today we’re totally over the moon to share that James is joining our team full-time.
James is an incredibly talented UI designer with an amazing eye for tiny details (they call him the shadow king) who is constantly pushing the industry forward and setting new trends while somehow never designing anything that feels trapped in a certain time period. He’s also a fantastic icon designer!
James has been a good friend of ours for a few years now, and we’ve worked with him on a few different projects over that time, including a bunch of awesome designs he put together for us for Tailwind UI when we wanted to add some fresh perspective last year.
We’ve been such huge fans of James’ work for so long that it’s honestly a dream come true to have the chance to work with him on what we’re doing with Tailwind CSS, Tailwind UI, Heroicons, and more.
Can’t wait to share some of the work we’ll be creating together in the months to come!
Today we’re excited to release Tailwind CSS: From Zero to Production, a new screencast series that teaches you everything you need to know to get up and running with Tailwind CSS v2.0 from scratch.
It’s an eight-part series totaling 1.5 hours of content, and walks you through everything from the initial setup process, to building out a responsive design with utility classes, to customizing your design system, to optimizing for production with PurgeCSS.
We started working with David Luhr last summer on a project-by-project basis to help us develop a Figma version of Tailwind UI(almost ready!), as well as to leverage his accessibility expertise when building Tailwind UI templates, ensuring we were following best practices and delivering markup that would work for everyone, no matter what tools they use to browse the web.
Today we’re excited to share that David has joined the team full-time!
David is an accessibility expert, a world-class front-end developer, a gifted educator, and a compassionate leader. He’s performed black magic with Tailwind UI in Figma that Steve and I didn’t even know was possible, and he’s been doing an incredible job turning Steve’s Tailwind UI designs into pixel-perfect HTML and CSS that works for everyone.
Say what you will about HTML being easy to learn, it’s a bear to master. David knows things about the spec that I’ve never encountered in 20 years of building things for the web, and has so much built up knowledge from his own real-world device testing that it would take years of dedicated focus to even come close to writing markup as bullet-proof as what David produces by default.
As a company that specializes in helping developers build better interfaces faster, it’s our responsibility to make sure that the tools we build follow accessibility best practices by default, and we couldn’t be more grateful to have David on the team to help us live up to that responsibility.
One of my favorite problems David has helped me solve is developing the new ring utilities in Tailwind CSS v2.0. When David first started auditing our work in Tailwind UI for accessibility improvements, he mentioned that some of our focus styles were not obvious enough. It turns out that just changing a button’s color for example isn’t good enough — it’s important that something new is drawn to the screen (like a focus ring) that is really easy for anyone to see.
Trying to come up with a way to solve this was hard. It needed to look good, needed to be straightforward to do with utility classes, and needed to be possible to actually implement in Tailwind internally. David suggested we study the interfaces of things like video games consoles or the Apple TV for inspiration since you can’t interact with them with a mouse, and that’s where we landed on trying to build some sort of customizable offset focus ring.
Coming up with an implementation for this was hard because it needed to be implemented with a box shadow, and we had to somehow make it composable with the existing box shadow API. There were many moments where I got frustrated and I might have even given up on it if I was working on it alone, but ultimately we figured it out and now it’s one of my favorite features in the framework.
David will be leading component and design asset development on Tailwind UI, and providing accessibility guidance on our other projects like Tailwind CSS and Headless UI. It’s been an amazing experience working with him over the last 6 months and we are so excited to have him on the team full-time.
Imagine you’re implementing a beautiful design you or someone on your team carefully crafted in Figma. You’ve nailed all the different layouts at each breakpoint, perfected the whitespace and typography, and the photography you’re using is really bringing the design to life.
It looks totally amazing — until you connect it your actual production content and realize that your beautiful grid of blog cards falls apart because, of course, real article excerpts aren’t all magically exactly three lines long, and now each card is a different height.
Sound familiar? If so, the line-clamp plugin is here to save your bacon.
A few weeks back we released @tailwindcss/line-clamp, an official Tailwind CSS plugin for truncating text to a specific number of lines.
Imagine you’re implementing a beautiful design you or someone on your team carefully crafted in Figma. You’ve nailed all the different layouts at each breakpoint, perfected the whitespace and typography, and the photography you’re using is really bringing the design to life.
It looks totally amazing — until you connect it your actual production content and realize that your beautiful grid of blog cards falls apart because, of course, real article excerpts aren’t all magically exactly three lines long, and now each card is a different height.
Sound familiar? If so, the line-clamp plugin is here to save your bacon.
First, install the plugin and add it to your tailwind.config.js file:
Then all you need to do is add a line-clamp-{n} utility to any block of text to automatically truncate to n lines with a trailing ellipsis:
<pclass="line-clamp-3">
Here's a block of text from a blog post that isn't conveniently three lines long like you designed
for originally. It's probably like 6 lines on mobile or even on desktop depending on how you have
things laid out. Truly a big pain in the derriere, and not the sort of thing you expected to be
wasting your time trying to deal with at 4:45pm on a Friday am I right? You've got tickets to
SmackDown and you heard there's gonna be a dark match with that local guy from two towns over that
your cousin went to high school with before the show starts, and you're gonna miss it if you're
not there early.
</p>
Almost exactly 18 months ago we released Tailwind CSS v1.0, which signalled a commitment to stability while continuing to push the boundaries with exciting new features in every minor release.
Over the course of those 18 months we released nine minor versions that added features like placeholder styling, screenreader visibility, CSS grid, transitions, transforms, animations, layout utilities, integrated tree-shaking, gradients, and tons more.
Today we’re finally releasing Tailwind CSS v2.0.
Tailwind CSS v2.0 is the first major update ever, including:
All-new color palette, featuring 220 total colors and a new workflow for building your own color schemes
Dark mode support, making it easier than ever to dynamically change your design when dark mode is enabled
New outline ring utilities, which are almost as good as if they would just make outline-radius a real thing
Utility-friendly form styles, a new form reset that makes it easy to customize form elements (even checkboxes) with just utility classes
Default line-heights per font-size, because if we can’t make using a 1.5 line-height with a 48px font illegal we should at least make it not the default
Extended spacing, typography, and opacity scales, for fine-tuning things at the micro level, making an even bigger impact with huge headlines, and for when opacity-25 wasn’t enough and opacity-50 was too much
Even though Tailwind CSS v2.0 is a new major version, we’ve worked really hard to minimize significant breaking changes, especially ones that would force you to edit tons of your templates. We’ve renamed two classes, removed three that are no longer relevant in modern browsers, and replaced two with more powerful alternatives. Any other breaking changes that might impact you can be remedied with a couple small additions to your tailwind.config.js file. Upgrading shouldn’t take more than about 30 minutes.
Check out the upgrade guide for more details and step-by-step instructions on migrating your project to Tailwind CSS v2.0.
We’ve learned a lot about color since the first time we tried to design a general purpose color palette back in the Tailwind CSS v0.1.0 days, and v2.0 represents our best attempt so far.
The new color palette includes 22 colors (compared to 10 previously) with 10 shades each (instead of 9) for a total of 220 values.
We’ve added an extra light 50 shade for every color, so they go from 50–900 now:
<divclass="bg-gray-50">I can't believe it's not white.</div>
The palette even includes 5 different shades of gray now, so you can choose “blue gray” if you want something really cool, or go all the way to “warm gray” for something with a lot more brown in it.
We configure a well-balanced 8-color palette for you out of the box, but the complete color palette lives in a new tailwindcss/colors module that you can import at the top of your config file to curate your own custom palette however you like:
Ever since iOS added native dark mode all you dark mode nerds haven’t been able to leave me alone about adding it to Tailwind. Well you did it, it’s here, you win.
Open up your tailwind.config.js file and flip darkMode to media:
tailwind.config.js
module.exports={darkMode:'media',// ...}
Boom — now just add dark: to the beginning of a class like bg-black and it’ll only take effect when dark mode is enabled:
<divclass="bg-white dark:bg-black"><h1class="text-gray-900 dark:text-white">Dark mode</h1><pclass="text-gray-500 dark:text-gray-300">
The feature you've all been waiting for.
</p></div>
You know how the outline property ignores border radius and pretty much just always looks bad? The ring utilities are our attempt to will a better solution into existence through blood, sweat, and tears.
They work a lot like the border utilities, except they add a solid box-shadow rather than a border so they don’t impact the layout:
Using a bunch of CSS custom property voodoo we’ve even made them automatically combine with regular box-shadows, too:
<buttonclass="shadow-sm focus:ring-2 ..."><!-- Both the shadow and ring will render together --></button>
The ring width documentation is the best starting point for learning these new APIs. They seriously turned out so cool, more useful than you probably think.
One thing I am constantly surprised by is how few people complain about how unbelievably useless form elements are out of the box with Tailwind. They literally look awful and you can’t do anything about it without writing custom CSS full of weird background-image SVG tricks and worrying about obscure edge cases that require CSS properties you’ve never heard of before like color-adjust.
I tried to solve this a while back with the @tailwindcss/custom-forms plugin, but something about adding a bunch of classes like form-input and form-checkbox just didn’t feel quite right so we didn’t really promote it and didn’t even link to it from the Tailwind documentation. This time though I think we figured it out.
Alongside Tailwind CSS v2.0, we’re releasing a brand new official plugin called @tailwindcss/forms that normalizes and resets all of the basic form controls across browsers to a state that is super easy to style with pure utility classes:
<!-- This will be a nice rounded checkbox with an indigo focus ring and an indigo checked state --><inputtype="checkbox"class="h-4 w-4 rounded border-gray-300 focus:border-indigo-300 focus:ring-2 focus:ring-indigo-200 focus:ring-opacity-50 text-indigo-500"/>
It’s not included out of the box but you can add it to your tailwind.config.js file with a single line:
We’ve extended the default spacing scale to include a bunch of micro values like 0.5, 1.5, 2.5, and 3.5:
<spanclass="ml-0.5">Just a little nudge.</span>
…as well as a bunch of new values at the top end as well like 72, 80, and 96:
<divclass="p-96">This is too much padding.</div>
We’ve also extended the inset (that’s top/right/bottom/left for you dinosaurs) and translate plugins to include the full spacing scale, so now you can do things like this:
<divclass="top-8"><!-- .... --></div>
We’ve extended the default typography scale with new 7xl, 8xl, and 9xl values:
<h1class="text-9xl font-bold">What is this, an Apple website?</h1>
And we’ve also extended the default opacity scale with steps of 10, as well as 5 and 95 values:
<figureclass="opacity-5"><blockquote>You can't see me.</blockquote><figcaption>John Cena</figcaption></figure>
Up until v2.0, if you wanted to control overflowing text all we really gave you was the fairly heavy-handed truncate utility.
Now we’ve added dedicated overflow-ellipsis and overflow-clip utilities to control just the text-overflow property, in case you wanted to add ellipsis to overflowing text without restricting that text to one line.
<pclass="overflow-ellipsis overflow-hidden">
Look ma no whitespace-nowrap ipsum...
</p>
In v2.0, we’ve made it possible to specify a default duration and timing function that is used automatically any time any transitionProperty utility is added:
We’ve decided to unburden ourselves with caring about IE11 at all, which has allowed us to fully embrace CSS custom properties for all sorts of crazy stuff and is what makes things like the new ring utilities even possible.
Dropping IE11 support means smaller builds even when using PurgeCSS, because we don’t have to ship any CSS variable fallbacks which adds up more than you’d expect.
Cheers to Bootstrap for having the cojones to do this first — I don’t think we would have been so bold if they hadn’t decided to pave the way.
The good news is that if you need to support IE11, you can always use Tailwind CSS v1.9 which is still an amazingly productive framework.
So there you have it folks, that’s Tailwind CSS v2.0 in a (pretty big) nutshell!
We just released Tailwind CSS v1.9 which adds support for configuration presets, useful new CSS grid utilities, extended border radius, rotate, and skew scales, helpful accessibility improvements, and more!
Tailwind CSS v1.9 adds a new presets key to the tailwind.config.js file that makes it possible to configure a custom “base configuration” for your projects.
tailwind.config.js
module.exports={presets:[require('@my-company/tailwind-base')],theme:{extend:{// Project specific overrides...},},}
Whatever you provide under presetsreplaces the default Tailwind base configuration, so you can define your own totally custom starting point. This is really helpful if you’re part of a team that works on multiple different Tailwind projects that all need to share the same brand colors, font customizations, or spacing scale.
You can even list multiple presets, which are merged together from top to bottom:
tailwind.config.js
module.exports={presets:[require('@my-company/tailwind-base'),require('@my-company/tailwind-marketing'),],theme:{extend:{// Project specific overrides...},},}
The logic to merge your project-specific configuration with your custom base configuration is exactly the same as how things work with the default configuration, so all of the features you’re used to like extend still work exactly the way you’d expect.
We’ve added new gridAutoColumns and gridAutoRows core plugins that add new utilities for the grid-auto-columns and grid-auto-rows CSS properties respectively.
These utilities let you control the size of implicitly-created grid columns and rows. Use them to set a default column/row size whenever you don’t specify a number of columns/rows for your grid.
Here’s a list of the new utilities that are included out of the box:
Class
CSS
auto-cols-auto
grid-auto-columns: auto;
auto-cols-min
grid-auto-columns: min-content;
auto-cols-max
grid-auto-columns: max-content;
auto-cols-fr
grid-auto-columns: minmax(0, 1fr);
auto-rows-auto
grid-auto-rows: auto;
auto-rows-min
grid-auto-rows: min-content;
auto-rows-max
grid-auto-rows: max-content;
auto-rows-fr
grid-auto-rows: minmax(0, 1fr);
We include responsive variants for these utilities by default, and they can be configured just like you’d expect under the gridAutoColumns and gridAutoRows sections of your tailwind.config.js file.
We’ve updated the outline-none class to render a transparent outline by default instead of rendering no outline. This is really helpful for people who use Windows high contrast mode, where custom box-shadow-based focus styles are completely invisible.
Now you can create custom focus styles using box shadows safely, without making your sites difficult to use for people with low vision.
We’ve also added two new outline styles: outline-white and outline-black.
These utilities render a 2px dotted outline in their respective color, with a 2px offset. They work great as general purpose unobtrusive focus indicators that make it easy for keyboard users to see which element on the screen is selected, without clashing too much with your design.
We’ve included both white and black variations so you can always be sure to have an option available that has sufficient contrast against whatever background color you’re using.
<!-- Use `outline-white` on dark backgrounds --><divclass="bg-gray-900"><buttonclass="... focus:outline-white"><!-- ... --></button></div><!-- Use `outline-black` on light backgrounds --><divclass="bg-white"><buttonclass="... focus:outline-black"><!-- ... --></button></div>
Of course, you’re also free to create whatever custom focus styles you like using background colors, box shadows, borders, whatever. These are great if you don’t want to get too fancy though.
We’ve made the outline property configurable as well, so you can now define custom outlines in your tailwind.config.js file:
We have promoted two previously experimental features (defaultLineHeights and standardFontWeights) to future, so we also recommend opting-in to those changes now to simplify the upgrade to Tailwind CSS v2.0 later this fall.
To get the most out of Tailwind, you need a build step. It’s the only way to be able to customize your tailwind.config.js file, extract components with @apply, or include plugins.
This isn’t a problem if you’ve already bought in to the framework, but if you’re just trying to kick the tires for the first time it’s a lot of friction. You either have to set up a local development environment with PostCSS support, or stick to the static CDN build, which means you lose out on lots of cool features.
So today we’re excited to release the first version of Tailwind Play, an advanced online playground for Tailwind CSS that lets you use all of Tailwind’s build-time features directly in the browser.
It includes support for all of Tailwind’s coolest features, plus tons of stuff that’s even better in Tailwind Play than it is in your editor, like:
Customizing your Tailwind theme
Enabling special variants, like group-hover or focus-within
Using custom directives in your CSS like @apply, @variants, and @responsive
Adding plugins like @tailwindcss/typography
Intelligent code completion and linting
Responsive design mode
One-click sharing
The code completion even updates the rendered preview in real-time, which creates an incredible design workflow in the browser — just navigate through different padding utilities with the arrow keys for example to find the perfect value without ever saving the file or even hitting enter!
Our responsive design mode that lets you fine-tune the viewport while you’re working on your design, just like you can in Chrome DevTools. You can even drag the viewport beyond the available space, and the preview area will automatically zoom out, letting you design for larger screens even when you have limited space.
One-click sharing really is just that — you don’t even need to create an account. Click “Share” and you’ve immediately got a link to a snapshot of what you’re working on that you can share online.
One of the biggest pain points when building modern web applications is building custom components like select menus, dropdowns, toggles, modals, tabs, radio groups — components that are pretty similar from project to project, but never quite the same.
You could use an off-the-shelf package, but they usually come tightly coupled with their own provided styles. It ends up being very hard to get them to match the look and feel of your own project, and almost always involves writing a bunch of CSS overrides, which feels like a big step backwards when working Tailwind CSS.
The other option is building your own components from scratch. At first it seems easy, but then you remember you need to add support for keyboard navigation, managing ARIA attributes, focus trapping, and all of a sudden you’re spending 3-4 weeks trying to build a truly bullet-proof dropdown menu.
We think there’s a better option, so we’re building it.
Headless UI is a set of completely unstyled, fully accessible UI components for React and Vue(and soon Alpine.js) that make it easy to build these sorts of custom components without worrying about any of the complex implementation details yourself, and without sacrificing the ability to style them from scratch with simple utility classes.
Here’s what it looks like to build a custom dropdown (one of many components the library includes) using @headlessui/react, with complete keyboard navigation support and ARIA attribute management, styled with simple Tailwind CSS utilities:
import{Menu}from'@headlessui/react'functionMyDropdown(){return(<Menuas="div"className="relative"><Menu.ButtonclassName="px-4 py-2 rounded bg-blue-600 text-white ...">Options</Menu.Button><Menu.ItemsclassName="absolute mt-1 right-0"><Menu.Item>{({ active })=>(<aclassName={`${active &&'bg-blue-500 text-white'} ...`}href="/account-settings">
Account settings
</a>)}</Menu.Item><Menu.Item>{({ active })=>(<aclassName={`${active &&'bg-blue-500 text-white'} ...`}href="/documentation">
Documentation
</a>)}</Menu.Item><Menu.Itemdisabled><spanclassName="opacity-75 ...">Invite a friend (coming soon!)</span></Menu.Item></Menu.Items></Menu>)}
Here’s what you’re getting for free in that example, without having to write a single line of code related to it yourself:
The dropdown panel opens on click, spacebar, enter, or when using the arrow keys
The dropdown closes when you press escape, or click outside of it
You can navigate the items using the up and down arrow keys
You can jump the first item using the Home key, and the last item using the End key
Disabled items are automatically skipped when navigating with the keyboard
Hovering over an item with your mouse after navigating with the keyboard will switch to mouse position based focusing
Items are announced properly to screen readers while navigating them with the keyboard
The dropdown button is properly announced to screenreaders as controlling a menu
…and probably tons more that I’m forgetting.
All without writing the letters aria anywhere in your own code, and without writing a single event listener. And you still have complete control over the design!
If you’ve followed my work online for the last few years, you might remember my fascination with renderless UI components — something I was really started getting into towards the end of 2017. I’ve wanted a library like this to exist for years, but until we started growing the team we just didn’t have the resources to make it happen.
Earlier this year we hired Robin Malfait, and he’s been working on Headless UI full-time ever since.
The biggest motivation for this project is that we’d really like to add production-ready JS examples to Tailwind UI, which is currently an HTML-only, bring-your-own-JavaScript sort of project. This is great for lots of our customers who want full control over how everything works, but for many others it’s a point of friction.
We didn’t want to add 200 lines of gnarly JS to every component example, so we started working on Headless UI as a way to extract all of that noise, without giving up any flexibility in the actual UI design.
We’re not the first people to try and tackle this problem. Downshift was the first library I saw that got me excited about this idea back in 2017, Reach UI and Reakit started development in 2018, and React Aria was released most recently, just earlier this year.
We decided to try our own take on the problem for a few reasons:
Existing solutions are focused almost entirely on React, and we’d like to bring these ideas to other ecosystems like Vue, Alpine, and hopefully more in the future.
These libraries are going to be foundational for adding JS support to Tailwind UI, and since that’s what keeps the business running it felt important to have complete decision-making power over how the libraries worked and what they supported.
We have our own ideas on what the APIs should look like for these components, and want to be able to explore those ideas freely.
We want to make sure it is always super easy to style these components with Tailwind, rather than having to write custom CSS.
We think what we’ve come up with so far hits a great balance between flexibility and developer experience, and we’re grateful there are other people working on similar problems that we can learn from and share our ideas with.
We’ve got quite a few more components to develop for Headless UI, including:
Modal
Radio group
Tabs
Accordion
Combobox
Datepicker
…and likely many more. We’re also about to start on Alpine.js support, and we’re hoping to be able to tag a v1.0 for React, Vue, and Alpine near the end of the year.
After that we’ll start exploring other frameworks, with the hope that we can eventually offer the same tools for ecosystems like Svelte, Angular, and Ember, either first-class or with community partners.
A lot of cool stuff has been added to Tailwind since the last time we published any screencasts, so we thought it would be a great idea to record a little series that covers all of the new additions.
“What’s new in Tailwind CSS?” is a series of 12 short videos that teach you everything you need to know about some of our favorite new Tailwind features.
A lot of cool stuff has been added to Tailwind since the last time we published any screencasts, so we thought it would be a great idea to record a little series that covers all of the new additions.
“What’s new in Tailwind CSS?” is a series of 12 short videos that teach you everything you need to know about some of our favorite new Tailwind features, including:
Back in February we released Tailwind UI, a directory of HTML component examples designed for you to copy and paste into your Tailwind projects as a starting point for your own designs.
We built Tailwind UI as an HTML-only, bring-your-own-JS product to make it as universal as possible, but many designs are inherently interactive and porting those interactive behaviors between JavaScript frameworks is unfortunately not always very easy.
One example of this is enter/leave transitions, like when you toggle a dropdown menu and see it fade in and out.
Vue.js has a really neat <transition> component for enter/leave transitions with a very utility-friendly API:
<transitionenter-active-class="transition-opacity duration-75"enter-from-class="opacity-0"enter-to-class="opacity-100"leave-active-class="transition-opacity duration-150"leave-from-class="opacity-100"leave-to-class="opacity-0"><divv-show="isShowing"><!-- Will fade in and out --></div></transition>
But replicating this in React turns out to be much more difficult, because until now there hasn’t been a library designed to support utility-driven transition styling.
So earlier this week, we released the very first version of @tailwindui/react, a library that provides low-level primitives for turning utility-first HTML into fully interactive UIs.
We’ll be adding many more components in the coming months (like dropdowns, toggles, modals, and more, and for Vue too!) but thought we’d start with a <Transition> component to at least get the current Tailwind UI experience for React users up to par with what’s possible in Vue and Alpine.js.
Here’s what it looks like to use:
import{Transition}from'@tailwindui/react'import{ useState }from'react'functionMyComponent(){const[isOpen, setIsOpen]=useState(false)return(<div><buttononClick={()=>setIsOpen(!isOpen)}>Toggle</button><Transitionshow={isOpen}enter="transition-opacity duration-75"enterFrom="opacity-0"enterTo="opacity-100"leave="transition-opacity duration-150"leaveFrom="opacity-100"leaveTo="opacity-0">{/* Will fade in and out */}</Transition></div>)}
A few months back we quietly released Heroicons, a set of free SVG icons we initially designed to support the components in Tailwind UI. Today we’re launching the official Heroicons web experience, which makes it easier than ever to search for icons and quickly copy them to your clipboard as Tailwind-ready HTML or JSX.
There are currently over 220 icons available in both medium and small sizes, with size designed to serve a different use-case:
Medium icons are designed to be rendered at 24x24, and work well for things like primary navigation and marketing sections.
Small icons are designed to be rendered at 20x20, and work well for buttons, form elements and to support text.
All of the icons are Tailwind-ready, and are easy to style with Tailwind’s built-in size and color utilities.
We’ve got lots of ideas for both new icons, as well as new icon styles (duotone anyone?) that we’re excited to design and release in the coming months.
Designing this site also got me itching to refresh the Hero Patterns site, so you’ll probably see something like this show up at heropatterns.com pretty soon:
We’ve got a bunch of other “Hero” domains waiting to be put to use too, and I’m pumped to reveal what we’re working on for those soon.
Another new Tailwind release is here! This time with support for gradients, background-clip, experimental support for using @apply with variant utilities, and tons more. Let’s dig in!
This is made possible by a new backgroundImage core plugin (which you can use for any background images you like!) and a new gradientColorStops core plugin.
The default configuration for these plugins looks like this:
For some dumb reason I named the column-gap and row-gap utilities col-gap-{n} and row-gap-{n} respectively, which isn’t terrible but it’s not consistent with how other things in Tailwind are named.
I was finding myself getting them wrong all the time — is row-gap the gaps in a row, or the gap between rows?
Tailwind v1.7 introduces new gap-x-{n} and gap-y-{n} utilities that do the exact same thing but have names that don’t suck. They make way more sense than the actual CSS names now that gap for flexbox is starting to roll out too, since flexbox has no “columns”.
These utilities will replace the old ones in v2.0, but for now they both exist together.
We recommend migrating to the new names now, and disabling the old names using this feature flag:
We’ve added a new contents class for the recent display: contents CSS feature.
<divclass="flex"><div><!-- ... --></div><!-- This container will act as a phantom container, and its children will be treated as part of the parent flex container --><divclass="contents"><div><!-- ... --></div><div><!-- ... --></div></div><div><!-- ... --></div></div>
You can now configure a default letter-spacing value for each font-size in your tailwind.config.js theme, using a tuple syntax:
tailwind.config.js
module.exports={theme:{fontSize:{
2xl:['24px',{letterSpacing:'-0.01em',}],// Or with a default line-height as well
3xl:['32px',{letterSpacing:'-0.02em',lineHeight:'40px',}],}}}
This new syntax is supported in addition to the simpler [{fontSize}, {lineHeight}] syntax that was recently introduced.
Currently the only thing passed through is an opacityVariable property, which contains the name of the current opacity variable (--background-opacity, --text-opacity, etc.) depending on which plugin is using the color.
Tailwind v1.7 introduces a new feature flagging and deprecation system designed to make upgrades as painless as possible.
Any time we deprecate functionality or introduce new (stable) breaking changes, they will be available in Tailwind v1.x under a future property in your tailwind.config.js file.
Whenever there are deprecations or breaking changes available, Tailwind will warn you in the console on every build until you adopt the new changes and enable the flag in your config file:
risk - There are upcoming breaking changes: removeDeprecatedGapUtilities
risk - We highly recommend opting-in to these changes now to simplify upgrading Tailwind in the future.
risk - https://tailwindcss.com/docs/upcoming-changes
You can opt-in to a breaking change by setting that flag to true in your tailwind.config.js file:
As mentioned previously, Tailwind v1.7.0 introduces new gap-x-{n} and gap-y-{n} utilities to replace the current col-gap-{n} and row-gap-{n} utilities.
By default both classes will exist, but the old utilities will be removed in Tailwind v2.0.
To migrate to the new class names, simply replace any existing usage of the old names with the new names:
Tailwind v1.7.0 introduces a new experimental feature system that allows you to opt-in to new functionality that is coming to Tailwind soon but isn’t quite stable yet.
It’s important to note that experimental features may introduce breaking changes, do not follow semver, and can change at any time.
If you like to live on the wild side though, you can enable all of them like so:
tailwind.config.js
module.exports={experimental:'all',}
With that out of the way, here is some of the fun stuff we’re working on that we’re pumped you can finally play with…
The idea behind the new palette is that every color at every shade has a similar perceived brightness. So you can swap indigo-600 with blue-600 and expect the same color contrast.
We do expect these colors to continue to change a lot as we iterate on them, so use these at your own risk.
We’ve added a much bigger spacing scale that includes new micro values like 0.5, 1.5, 2.5, and 3.5, as well as new large values like 72, 80, and 96, and added percentage based fractional values to the whole spacing scale (1/2, 5/6, 7/12, etc.)
You can enable the extended spacing scale using the extendedSpacingScale flag:
We’ve added three new font sizes (7xl, 8xl, and 9xl) to keep up with the latest huge-as-hell-hero-text trends. They include default line-heights as well.
You can enable them under the extendedFontSizeScale flag:
Back in May we published our first job posting to help us find a full-stack developer to join our team.
After receiving almost 900 applications and interviewing dozens of talented people, we’re excited to finally share that Robin Malfait accepted our offer for the position and is officially part of the Tailwind Labs team as of today!
Robin is a talented developer from Belgium, and has been an active member of the Tailwind community for a long time. If you’re a Tailwind UI customer and have ever asked a question in the #react channel on our Discord server, there’s a 90% chance he’s the helpful person who answered your question. He even built a bookmarklet to help people convert Tailwind UI components to JSX!
Robin is a seriously experienced React developer, and is joining us to help spearhead the open-source renderless UI libraries we are working on that will be the foundation for official React and Vue (to start anyways) support in Tailwind UI.
We’re super excited that he is finally starting with us today, and can’t wait to watch his contributions enable people to build awesome UIs even faster and with more confidence. Welcome to the team dude!
What follows is the story of how we went about hiring for this role, and how we narrowed down the candidates from almost 900 initial applications to finally making an offer to Robin.
Before this role, we had only hired Brad who we already knew and trusted, so we didn’t need a job posting or any sort of rigorous application process.
I knew that if we wanted to get really great candidates, we had to write a compelling job posting. After about 3-4 days of working on it, this is where we ended up:
Here are the important things I focused on when writing it:
Be specific about the projects the applicant would be working on after they started
Be clear that we are a small team, so everyone has to do a bit of everything, including customer support
Give concrete examples of projects we just finished that the applicant would have worked on if they were already at the company
Go into detail about specific hard problems we expect to run into on the next major upcoming project, to help the applicant understand the sort of expertise that would be valuable to us
Share concrete salary and benefit information. I would never apply for a job without a clear understanding of the salary, so why should I expect talented people to apply for our posting without it?
We got tons of positive feedback about this posting, and I’m really proud of how it turned out. I think it was very applicant-centric, and I think it made a big difference in the quality of submissions we got.
One thing we did a bit differently from other companies is that we didn’t ask for a resume or give applicants a big list of questions to answer. All we asked for was an “application”, in whatever form the person decided. It could be a cover letter, a small website, a video, a slide deck, whatever.
I decided to ask for applications this way for a few reasons:
I just don’t think resumes are that important
I wanted to filter for people with some inherent marketing sensibilities, we’re a tiny company so we need T-shaped people more than we need specialists
I wanted to filter for people who can ship things, and making the application completely free-form tells you a lot about someone’s ability to take something from nothing to polished product on their own
I wanted to find someone who talked about the stuff we were looking for without being prompted for it — finding someone who was naturally well-aligned with what we are trying to do would be a big advantage for us
I expected a lot of applications, and I thought asking for applications this way would make it easy to filter people out who were using a shotgun approach to job-searching and not specifically interested in working with us
Even with what I think was a fairly intimidating application process, we got well over 100 applications where there was clearly a lot of time spent crafting something very specific for our posting, including Robin’s of course:
Some people did some really out there and creative things in their applications (one person even made an interactive game!) but Robin’s stood out to us for a few reasons:
The visual design was great. We’re a very design-focused company, so having good taste in design is really important to us.
His story about learning to program and getting into the Laravel community told me we had a rich shared history, even if we had never met.
He took a chance and shared some strong opinions he had about component design that were extremely relevant to some work we’ll be doing very soon, and I agreed with what he was saying and even learned a few things.
He shared a super interesting open-source library he authored, which despite being very unknown, still had very well thought-out and complete documentation that was presented in a very well-structured way. It was clear he thinks about visual design even when authoring a markdown file.
He shared lots of concrete ideas for projects he’d like to work on with us, and a lot of them were things I was already excited about doing.
He capitalized the “H” in “GitHub” (holy shit I hate when people don’t do that).
Robin’s was one of maybe 40-50 that really stood out from a content perspective.
Dealing with almost 900 job applications is a lot of work. Over half of them we were able to discard immediately because they just provided a link to their LinkedIn profile or a generic resume, but filtering through the rest was really tough.
I’ve never hired anyone this way before, and at first I really felt like we needed to meet with and interview everyone who submitted a quality application. As the applications poured in though, I realized this was just not practical, and that we had to put some sort of cap on it.
I decided to sort the good applications as best I could, then just slice off the top 20 and start there. It meant there were lots of great people we wouldn’t talk to and that maybe we even missed out on the absolute best applicant, but the reality is that we only have so much time we can dedicate to this, and I had to believe that out of the ~20 best applications, there would certainly be multiple people we wouldn’t regret hiring at all, even if there was a chance that the absolute best person was somewhere in the other 30.
We started by scheduling video interviews with the top ~20 applicants, which took about 3 weeks to get through.
These were 30-45 minute calls where we had a pretty casual conversation about a few topics:
What the person had been working on recently, and where they think their strengths are
Why they applied for the job, and what about the role was interesting to them
What we as a company are going to be doing over the next year or so, and digging into a few projects in detail
Answering any questions the person had about the job or our company
This was a great way just to get to know the people who applied and get a gut sense for who stood out the most. We really enjoyed meeting with every single person we talked to, but made the hard decision to filter down again to about 10 people for the next phase.
The next step in the application process was a take-home project, where the applicant had to build out a design Steve had created using either Vue or React. We estimated it to be about a 4-8 hour project.
We provided a zip file containing all of the instructions, the design as a Figma file, and a walk-through video of a working implementation outlining any behavior that was hard to capture in Figma.
We tried to give very clear instructions, and made sure to point out where we wanted people to focus their time, and what areas we didn’t want them to overthink or spend too much time on.
We gave each candidate about two weeks to complete the project, just to make sure they had the opportunity to fit it into their schedule without it being disruptive.
All of the submissions we got back were great, but again we forced ourselves to limit the candidates for the next phase, this time down to 6 people.
One thing we really loved about Robin’s submission was that he spent a lot of time guiding us through his solution with comments in the code. For regular production code I would say it was definitely overkill, but as part of a job application I thought it was extremely helpful to get a behind-the-scenes look into how he actually thinks about the code he is writing. He also spent a lot of time describing alternate solutions to certain problems and why he didn’t go with those approaches, which was very beneficial as well.
The final step in the application process was a two-hour pair programming session with me.
When pairing as part of an interview process like this, there’s a really high risk of the inherent power dynamic coloring how the whole thing goes. I really wanted to avoid that as much as possible, so I did two things:
I made sure whatever we were pairing on was something completely new, that I had no prior experience with
I let the candidate suggest a few things for us to pair on, and picked something from their list
I absolutely didn’t want to pair on something where I knew all the answers and I was just watching to see if the candidate could figure out something I already knew. That is absolutely not representative of real work and I don’t think it would’ve been useful at all.
Instead, by choosing a problem that neither of us had significant experience with, we got to put the power dynamic aside (as much as possible at least), and just focus on learning something new together, and seeing how we helped each other get unstuck.
Some of the things I paired on included:
Building a date picker from scratch
Learning XState
Building a modal dialog with the Vue 3 composition API
I really enjoyed this process and am very proud of how we put it together. It was definitely the most informative part of the interview process and really gave me a ton of confidence that we were offering the job to the right person.
For Robin’s session, we decided to build an SVG charting library from scratch (something neither of us had ever done before), in Svelte (a framework neither of us had ever used before). This was Robin’s idea, and that he had the courage to tackle two completely new problems at the same time in an interview context really impressed me. We had a great time pairing together on this, and not once in the session did it ever feel like either of us was ahead of the other person or trying to catch them up on something. We had really great chemistry and it felt very energizing and productive, and reminded me of some of the best pairing sessions I’ve had in my career, which is pretty incredible given we’d never worked together before, and that he was being evaluated for a job.
This whole process took about 1.5 months, and at the end we had a very hard time choosing between the top few candidates. Realistically we could’ve hired any of them and not regretted it, but my experience interviewing and pairing with Robin stood out just a bit more and I was really excited to be able to offer him the role. We know he’s going to be an amazing fit for the team, and I can’t wait to dig in to some hard problems with him in the coming months.
It’s like Tailwind CSS v1.5 except now there’s animation support, overscroll utilities, and more!
There aren’t supposed to be any breaking changes here, but I thought that last time too. If I did break something, first person to report it gets a Tailwind shirt.
To go along with the new animation features, we’ve also added new motion-safe and motion-reduce variants that allow you to conditionally apply CSS based on the prefers-reduced-motion media feature.
These can be useful in conjunction with transition and animation utilities to disable problematic motion for users who are sensitive to it:
These can be combined with responsive variants and pseudo-class variants as well:
<!-- With responsive variants --><divclass="sm:motion-reduce:translate-y-0"></div><!-- With pseudo-class variants --><divclass="motion-reduce:hover:translate-y-0"></div><!-- With responsive and pseudo-class variants --><divclass="sm:motion-reduce:hover:translate-y-0"></div>
These are currently not enabled for any utilities by default, but you can enabled them as needed in the variants section of your tailwind.config.js file:
You can use these utilities to control how “scroll chaining” works in your sites, and avoid scrolling the whole page when you reach the top or bottom of an embedded scrollable area.
Note that this is currently not supported in Safari, but in my opinion it’s not a huge deal to treat this as a progressive enhancement anyways, since it falls back fairly gracefully.
This plugin can be configured in your tailwind.config.js file as overscrollBehavior:
tailwind.config.js
module.exports={// ...// Disabling the plugincorePlugins:{overscrollBehavior:false,},// Customizing the enabled variantsvariants:{overscrollBehavior:['responsive','hover'],},}
…then I’ve got news for you baby — if you’re using our tailwindcss CLI tool you can start depositing those 58 characters into your savings account instead of wasting them on a pointless CSS file.
The input file argument is now optional in the CLI tool, so if you don’t actually need a custom CSS file, you can just write this:
npx tailwindcss build -o compiled.css
Your kids are going to be so grateful for the extra time you get to spend together.
Today we are super excited to share that Simon Vrachliotis has joined the development team at Tailwind Labs! (We just finalized that new business name by the way, pretty cool right?)
Steve and I met Simon for the first time when we visited Sydney for Laracon AU back in 2018, where Simon was giving a talk on, of course, utility-first CSS:
He knocked it out of the park, and it has been an absolute pleasure getting to know Simon better over the time since we first met.
Simon is a talented developer, an amazing teacher, and has such a contagious enthusiasm for the sort of work we do that we knew we had to have him on the team if we ever had the chance.
He’s joining in a product and community focused role, and will be doing lots of amazing work helping us build things like Tailwind UI, as well as creating educational resources to help even more people have success with Tailwind CSS.
We couldn’t be more excited to be welcoming him to the team!
Back in June, Brad Cornes joined our company as our very first team member. We didn’t have a blog to announce it back then, but better late than never right?
You might know Brad as the creator of the amazing Tailwind CSS IntelliSense plugin for VS Code, which he first released way back in 2018 and has since been installed over 100,000 times!
Brad has been using Tailwind since it was first released, and I got to know Brad in the really early days of building the Tailwind community. I was immediately impressed by his willingness to tackle extremely hard problems, his ability to come up with out-of-the-box creative solutions, and his propensity for diving deep into bleeding-edge technologies and finding interesting use-cases for them.
When we decided we wanted to grow the team, Brad was the first person that came to mind and I reached out to him about the idea way back in March. He’s been with us for over a month now and it has been absolutely awesome working with him and benefitting from his extensive experience and expertise.
Brad has been helping us out all over the place, working on things like the IntelliSense plugin, developing internal tooling for Tailwind UI, and even building this very blog. He has an amazing ability to take a hard, complex problem, lock himself in a room for 2 hours, and come out the other side with an elegant solution.
He’s been an amazing addition to the team, and we are extremely excited about all the new things we’re going to be building in the coming months that would not be possible without his help.
Follow Brad on Twitter to keep up with what he’s working on and for sneak peaks of some exciting new projects!
I was hoping to save v1.5.0 for something really exciting but we needed a new feature to support the new @tailwindcss/typography plugin so h*ck it, we’re dropping some new stuff on you early.
No breaking changes, this is a minor release and we’re professionals you silly goose.
I was hoping to save v1.5.0 for something really exciting but we needed a new feature to support the new @tailwindcss/typography plugin so h*ck it, we’re dropping some new stuff on you early.
No breaking changes, this is a minor release and we’re professionals you silly goose.
Until Tailwind CSS v1.5.0, only “utility” classes were really intended to be used with variants (like “responsive”, “hover”, “focus”, etc.)
While these are still much more useful for utilities than any other type of class, we now support generating variants for component classes as well, like the prose classes in the new @tailwindcss/typography plugin:
You can take advantage of this feature in your own component classes by using the new variants option in the second argument of the addComponents plugin API:
To take advantage of these feature in your custom CSS (rather than using the plugin API), you can use a new @layer directive to explicitly tell Tailwind that your styles belong to the “components” bucket:
@layer components{@responsive{.card{/* ... */}}}
This helps Tailwind purge your unused CSS correctly, ensuring it doesn’t remove any responsive component variants when using the default “conservative” purge mode.
Piggy-backing off of the new component variants support, the container class now supports variants!
<!-- Only lock the width at `md` sizes and above --><divclass="md:container"><!-- ... --></div>
We’ve enabled responsive variants by default, but if you are sick in the head you can also manually enable other variants like focus, group-hover, whatever:
Browser support is still pretty weak on this but getting better. In the mean time, check out the polyfill and corresponding PostCSS plugin if you’d like to use this in all browsers right away.
Until now, trying to style an article, document, or blog post with Tailwind has been a tedious
task that required a keen eye for typography and a lot of complex custom CSS.
That’s why today we’re excited to release @tailwindcss/typography — a plugin that lets you easily style vanilla HTML content with beautiful typographic defaults.
Until now, trying to style an article, document, or blog post with Tailwind has been a tedious
task that required a keen eye for typography and a lot of complex custom CSS.
By default, Tailwind removes all of the default browser styling from paragraphs, headings, lists and more. This ends up being really useful for building application UIs because you spend less time undoing user-agent styles, but when you really are just trying to style some content that came from a rich-text editor in a CMS or a markdown file, it can be surprising and unintuitive.
We get lots of complaints about it actually, with people regularly asking us things like:
Why is Tailwind removing the default styles on my h1 elements? How do I disable this? What do you mean I lose all the other base styles too?
We hear you, but we’re not convinced that simply disabling our base styles is what you really want. You don’t want to have to remove annoying margins every time you use a p element in a piece of your dashboard UI. And I doubt you really want your blog posts to use the user-agent styles either — you want them to look awesome, not awful.
That’s why today we’re excited to release @tailwindcss/typography — a plugin that gives you what you actually want, without any of the downside of doing something stupid like disabling our base styles.
It adds a new set of prose classes that you can slap on any block of vanilla HTML content and turn it into a beautiful, well-formatted document:
<articleclass="prose lg:prose-xl"><h1>Garlic bread with cheese: What the science tells us</h1><p>
For years parents have espoused the health benefits of eating garlic bread with cheese to their
children, with the food earning such an iconic status in our culture that kids will often dress
up as warm, cheesy loaf for Halloween.
</p><p>
But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
springing up around the country.
</p><!-- ... --></article>
So how does it actually look? Well you’re looking at it right now — we use it to style the content on this very blog!
One of the things we believe as a team is that everything we make should be sealed with a blog post. Forcing ourselves to write up a short announcement post for every project we work on acts as a built-in quality check, making sure that we never call a project “done” until we feel comfortable telling the world it’s out there.
The problem was that up until today, we didn’t actually have anywhere to publish those posts!
We’re a team of developers so naturally there was no way we could convince ourselves to use something off-the-shelf, and opted to build something simple and custom with Next.js.
There are a lot of things to like about Next.js, but the primary reason we decided to use it is that it has great support for MDX, which is the format we wanted to use to author our posts.
# My first MDX post
MDX is a really cool authoring format because it lets
you embed React components right in your markdown:
<MyComponentmyProp={5}/>
How cool is that?
MDX is really interesting because unlike regular Markdown, you can embed live React components directly in your content. This is exciting because it unlocks a lot of opportunities in how you communicate ideas in your writing. Instead of relying only on images, videos, or code blocks, you can build interactive demos and stick them directly between two paragraphs of content, without throwing away the ergonomics of authoring in Markdown.
We’re planning to do a redesign and rebuild of the Tailwind CSS documentation site later this year and being able to embed interactive components makes a huge difference in our ability to teach how the framework works, so using our little blog site as a test project made a lot of sense.
We started by writing posts as simple MDX documents that lived directly in the pages directory. Eventually though we realized that just about every post would also have associated assets, for example an Open Graph image at the bare minimum.
Having to store those in another folder felt a bit sloppy, so we decided instead to give every post its own folder in the pages directory, and put the post content in an index.mdx file.
We store metadata about each post in a meta object that we export at the top of each MDX file:
import{ bradlc }from'@/authors'importopenGraphImagefrom'./card.jpeg'exportconst meta ={title:'Introducing linting for Tailwind CSS IntelliSense',description:`Today we’re releasing a new version of the Tailwind CSS IntelliSense extension for Visual Studio Code that adds Tailwind-specific linting to both your CSS and your markup.`,date:'2020-06-23T18:52:03Z',authors:[bradlc],image: openGraphImage,discussion:'https://github.com/tailwindcss/tailwindcss/discussions/1956',}// Post content goes here
This is where we define the post title (used for the actual h1 on the post page and the page title), the description (for Open Graph previews), the publish date, the authors, the Open Graph image, and a link to the GitHub Discussions thread for the post.
We store all of our authors data in a separate file that just contains each team member’s name, Twitter handle, and avatar.
The nice thing about actually importing the author object into a post instead of connecting it through some sort of identifier is that we can easily add an author inline if we wanted to:
exportconst meta ={title:'An example of a guest post by someone not on the team',authors:[{name:'Simon Vrachliotis',twitter:'@simonswiss',avatar:'https://pbs.twimg.com/profile_images/1160929863/n510426211_274341_6220_400x400.jpg',},],// ...}
This makes it easy for us to keep author information in sync by giving it a central source of truth, but doesn’t give up any flexibility.
We wanted to display previews for each post on the homepage, and this turned out to be a surprisingly challenging problem.
Essentially what we wanted to be able to do was use the getStaticProps feature of Next.js to get a list of all the posts at build-time, extract the information we need, and pass that in to the actual page component to render.
The challenge is that we wanted to do this without actually importing every single page, because that would mean that our bundle for the homepage would contain every single blog post for the entire site, leading to a much bigger bundle than necessary. Maybe not a big deal right now when we only have a couple of posts, but once you’re up to dozens or hundreds of posts that’s a lot of wasted bytes.
We tried a few different approaches but the one we settled on was using webpack’s resourceQuery feature combined with a couple of custom loaders to make it possible to load each blog post in two formats:
The entire post, used for post pages.
The post preview, where we load the minimum data needed for the homepage.
The way we set it up, any time we add a ?preview query to the end of an import for an individual post, we get back a much smaller version of that post that just includes the metadata and the preview excerpt, rather than the entire post content.
Here’s a snippet of what that custom loader looks like:
It lets us define the excerpt for each post either by sticking <!--more--> after the intro paragraph, or by wrapping the excerpt in a pair of <!--excerpt--> and <!--/excerpt--> tags, allowing us to write an excerpt that’s completely independent from the post content.
export const meta = {
// ...
}
This is the beginning of the post, and what we'd like to
show on the homepage.
<!--more-->
Anything after that is not included in the bundle unless
you are actually viewing that post.
Solving this problem in an elegant way was pretty challenging, but ultimately it was cool to come up with a solution that let us keep everything in one file instead of using a separate file for the preview and the actual post content.
The last challenge we had when building this simple site was being able to include links to the next and previous post whenever you’re viewing an individual post.
At its core, what we needed to do was load up all of the posts (ideally at build-time), find the current post in that list, then grab the post that came before and the post that came after so we could pass those through to the page component as props.
This ended up being harder than we expected, because it turns out that MDX doesn’t currently support getStaticProps the way you’d normally use it. You can’t actually export it directly from your MDX files, instead you have to store your code in a separate file and re-export it from there.
We didn’t want to load this extra code when just importing our post previews on the homepage, and we also didn’t want to have to repeat this code in every single post, so we decided to prepend this export to the beginning of each post using another custom loader:
{use:[...mdx,createLoader(function(src){const content =['import Post from "@/components/Post"','export { getStaticProps } from "@/getStaticProps"',
src,'export default (props) => <Post meta={meta} {...props} />',].join('\n')if(content.includes('<!--more-->')){returnthis.callback(null, content.split('<!--more-->').join('\n'))}returnthis.callback(null, content.replace(/<!--excerpt-->.*<!--\/excerpt-->/s,''))}),],}
We also needed to use this custom loader to actually pass those static props to our Post component, so we appended that extra export you see above as well.
This wasn’t the only issue though. It turns out getStaticProps doesn’t give you any information about the current page being rendered, so we had no way of knowing what post we were looking at when trying to determine the next and previous posts. I suspect this is solvable, but due to time constraints we opted to do more of that work on the client and less at build time, so we could actually see what the current route was when trying to figure out which links we needed.
We load up all of the posts in getStaticProps, and map them to very lightweight objects that just contain the URL for the post, and the post title:
This works well enough for now, but again long-term I’d like to figure out a simpler solution that lets us load only the next and previous posts in getStaticProps instead of the entire thing.
There’s an interesting library by Hashicorp designed to make it possible to treat MDX files like a data source called Next MDX Remote that we will probably explore in the future. It should let us switch to dynamic slug-based routing which would give us access to the current pathname in getStaticProps and give us a lot more power.
Overall, building this little site with Next.js was a fun learning experience. I’m always surprised at how complicated seemingly simple things end up being with a lot of these tools, but I’m very bullish on the future of Next.js and looking forward to building the next iteration of tailwindcss.com with it in the months to come.
If you’re interested in checking out the codebase for this blog or even submitting a pull request to simplify any of the things I mentioned above, check out the repository on GitHub.
Tailwind already detects CSS errors, for example when you mistype a screen name in the @screen directive. The linting feature for Tailwind CSS IntelliSense surfaces these errors and displays them in context, directly inside your editor. The linter will validate your @tailwind, @screen, @variants and @apply directives, as well as any theme function calls:
There is one more lint rule which analyses class lists in your template files and highlights any instances where utilities seem to be in conflict. For example you probably didn’t intend to have mt-4 and mt-6 in the same class list!
To make it as easy as possible to fix any issues, all of the lint rules have their own “quick fixes” which can be triggered directly within Visual Studio Code. If you accidentally typed @screen small instead of @screen sm, the editor can automatically replace small with sm for you!
As well as simple text replacements there’s also some more interesting quick fixes for the more complex lint rules. Take a look at how the extension can automatically refactor an invalid @apply directive:
We think you’ll love the new lint feature, but if you don’t, or you just want to tweak some behavior, we’ve got you covered. You can decide how each rule violation is treated: is it an error, or just a warning, or do you want to ignore the rule altogether? If you really want to you can disable linting entirely using the new tailwindCSS.validate setting.
Check out the extension readme for more details about configuring the lint rules to suit your workflow.
Linting is available now in v0.4.0 of Tailwind CSS IntelliSense! If you already have the extension you may need to reload Visual Studio Code to get the update, and if you don’t you can install it via the extension marketplace.
This is the very first iteration of this feature, and we’d love to hear your feedback! Do you have an idea for a new lint rule? Let us know!