releases.shpreview
Tailwind CSS/Tailwind CSS Blog

Tailwind CSS Blog

Mon
Wed
Fri
JunJulAugSepOctNovDecJanFebMarAprMay
Less
More
Releases1Avg0/wkVersionsv4.3
v4.3

Tailwind CSS v4.3 is here, and because apparently shipping v4.2 was easier than remembering to blog about it, this post is secretly about two releases worth of new stuff.

Here's what landed in v4.2:

  • New mauve, olive, mist, and taupe palettes — four more neutral-ish palettes for when gray, zinc, neutral, stone, and slate have somehow failed you.
  • First-class webpack plugin — for a huge performance boost in frameworks like Next.js.
  • More logical property utilities — including pbs-*, mbs-*, inline-*, block-*, and new logical inset utilities.
  • New font-features-* utility — control low-level OpenType features without writing custom CSS every time.

…and here's what's new in v4.3:

  • New scrollbar utilities — style scrollbars without wondering which half-supported browser API you're supposed to use this week.
  • New @container-size utility — create size containers for when your container queries need to care about height too.
  • New zoom-* utilities — use the CSS zoom property directly in your markup, now that every browser has finally agreed it exists.
  • New tab-* utilities — control the rendered width of tab characters, just please don't make it eight like GitHub used to.
  • Stacked + compound @variant support — use stacked and compound variants directly in CSS.
  • Default values for functional utilities — define utilities that work with or without a value.

A lot of the v4.2 improvements, including the new logical property utilities and font-features-* support, came out of collaborations with teams at Netflix and Vercel through our Partners Program. If there are framework improvements like this that would make Tailwind CSS work better for your team, we'd love to help.

Upgrade your projects by installing the latest version of tailwindcss from npm:

Using the Tailwind CLI

npm install tailwindcss@latest @tailwindcss/cli@latest

Using Vite

npm install tailwindcss@latest @tailwindcss/vite@latest

Using PostCSS

npm install tailwindcss@latest @tailwindcss/postcss@latest

Using webpack

npm install tailwindcss@latest @tailwindcss/webpack@latest

New mauve, olive, mist, and taupe palettes

In v4.2 we added four new color palettes to the default theme: mauve, olive, mist, and taupe.

These originally came out of Oatmeal, the multi-theme marketing site kit we released for Tailwind Plus back in December, where we needed a few more neutral-ish palettes that still felt distinct from the grays we already ship.

They're all in that useful neutral-adjacent category where you want something that still behaves like gray, but has a little more personality:

They're a nice substitute for the existing gray palettes when you want the whole design to lean a little warmer, cooler, greener, or… mauver?

<div class="bg-mauve-950 text-mauve-100 ...">Mauve</div>
<div class="bg-olive-100 text-olive-950 ...">Olive</div>
<div class="border border-mist-200 shadow-taupe-950/10 ...">Mist and taupe</div>

Check out the colors documentation for a pretty grid of all the available colors.


First-class webpack plugin

Tailwind CSS v4.2 added a dedicated @tailwindcss/webpack loader for integrating Tailwind CSS in webpack projects.

Before this, webpack projects usually ran Tailwind through postcss-loader and @tailwindcss/postcss, which works, but means CSS has to take a little detour through PostCSS just so Tailwind can get its hands on a string it already knows how to compile.

The dedicated loader skips that extra work, and we've seen it make Tailwind over 2x faster in large, complicated webpack projects. For example, here's what we saw testing against the tailwindcss.com docs with Next.js and Turbopack:

PackageBuild time
@tailwindcss/postcss932ms
@tailwindcss/webpack429ms
Speedup2.17x faster

Using it is the same sort of setup you're used to from other webpack loaders:

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader", "@tailwindcss/webpack"],
      },
    ],
  },
};

Because Turbopack supports webpack loaders through its compatibility layer, these improvements carry over there too, which is a big deal for frameworks like Next.js where Turbopack is becoming the default.

Check out the @tailwindcss/webpack README for more details.


More logical property utilities

Tailwind CSS v4.2 added a whole bunch of new logical property utilities, making it easier to build layouts that adapt correctly across different writing modes and directions.

For spacing, there are new block-start and block-end utilities for padding, margin, scroll padding, scroll margin, and borders:

<div class="mbs-6 mbe-2 pbs-4 pbe-8 ...">
  <!-- ... -->
</div>
<div class="scroll-mbs-4 scroll-mbe-4 scroll-pbs-12 scroll-pbe-12 ...">
  <!-- ... -->
</div>
<div class="border-bs border-be-2 ...">
  <!-- ... -->
</div>

We've also added logical sizing utilities for inline-size and block-size, along with min and max variants:

<div class="block-64 inline-full max-block-screen max-inline-lg min-block-24 min-inline-0 ...">
  <!-- ... -->
</div>

And we've added logical inset utilities for positioning elements:

<div class="absolute inset-s-0 inset-e-4 inset-bs-2 inset-be-8 ...">
  <!-- ... -->
</div>

The existing start-* and end-* utilities are still available, but they're now deprecated in favor of inset-s-* and inset-e-* so the whole API lines up with inset-bs-* and inset-be-*.

Check out the padding, margin, scroll-padding, scroll-margin, border-width, inline-size, block-size, and top / right / bottom / left docs for more details.


New font-features-* utility

We've also added font-features-* utilities for controlling font-feature-settings:

<div class='font-features-["tnum"] ...'>
  <!-- ... -->
</div>

For common cases like tabular numbers, you should still reach for higher-level utilities like tabular-nums first, because nobody should have to remember that "tnum" is a thing.

But when you need to enable a font-specific stylistic set or some other OpenType feature we don't have a dedicated utility for, font-features-* gives you a clean escape hatch.

Check out the font-feature-settings docs for more details.


New scrollbar utilities

Tailwind CSS v4.3 adds first-party utilities for the CSS scrollbar APIs, so you can finally do the boring-but-useful scrollbar stuff without opening a second tab and remembering which browser supports what.

Use scrollbar-auto, scrollbar-thin, and scrollbar-none to control scrollbar-width:

<div class="scrollbar-thin scrollbar-thumb-sky-700 scrollbar-track-sky-100 overflow-auto ...">
  <!-- ... -->
</div>

You can also control scrollbar colors using scrollbar-thumb-* and scrollbar-track-* utilities:

<div class="scrollbar-thumb-sky-700 scrollbar-track-sky-100 overflow-auto ...">
  <!-- ... -->
</div>

They work with the usual color opacity modifiers too:

<div class="scrollbar-thumb-slate-900/60 scrollbar-track-slate-900/10 ...">
  <!-- ... -->
</div>

And for preventing layout shift when scrollbars appear, we've added scrollbar-gutter-auto, scrollbar-gutter-stable, and scrollbar-gutter-both:

<div class="scrollbar-gutter-stable overflow-auto ...">
  <!-- ... -->
</div>

Check out the scrollbar-width, scrollbar-color, and scrollbar-gutter docs for more details.


New @container-size utility

In Tailwind CSS v4.0 we added first-party container query support with the @container utility, which creates an inline-size container.

That's what you want most of the time, but container query length units like cqb and cqh depend on the block size of the container, and inline-size containers don't expose that information.

So in v4.3 we've added @container-size, which creates a size container instead:

<div class="@container-size">
  <div class="h-[50cqb]">
    <!-- ... -->
  </div>
</div>

You can name size containers too using @container-size/{name}, just like @container/{name}.

Check out the responsive design docs for more details.


New zoom-* utilities

Tailwind CSS v4.3 adds zoom-* utilities for the CSS zoom property, one of those ancient browser features that was supported in Chrome 1 back in 2008, but only became fully supported across all major browsers in 2024 after taking the scenic route through a few long-standing SVG bugs:

<div class="zoom-75 ...">Zoomed out</div>
<div class="zoom-100 ...">Normal</div>
<div class="zoom-125 ...">Zoomed in</div>

You can also use arbitrary values and CSS variables:

<div class="zoom-[1.1] ...">Zoomed in a little</div>
<div class="zoom-(--preview-zoom) ...">Zoomed using a variable</div>

Check out the zoom docs for more details.


New tab-* utilities

We've also added tab-* utilities for controlling the rendered width of tab characters using tab-size.

This is mostly useful for code examples, editors, and any other UI where you're rendering preformatted text that might contain real tab characters:

<pre class="tab-2 ...">function indent() {
	return 'tabbed';
}</pre>
<pre class="tab-8 ...">function indent() {
	return 'tabbed';
}</pre>

Arbitrary values and CSS variables work here too:

<pre class="tab-[12px] ...">...</pre>
<pre class="tab-(--tab-size) ...">...</pre>

Check out the tab-size docs for more details.


Stacked + compound @variant support

Tailwind CSS v4.3 makes @variant more flexible when you're using variants in CSS instead of directly in your markup.

You can now use stacked variants:

CSS

.button {
  background: var(--color-sky-500);

  @variant hover:focus {
    background: var(--color-sky-600);
  }
}

Compiled CSS

.button {
  background: var(--color-sky-500);
  &:hover {
    @media (hover: hover) {
      &:focus {
        background: var(--color-sky-600);
      }
    }
  }
}

And you can use compound variants to share the same block of CSS across multiple variants:

CSS

.button {
  background: var(--color-sky-500);

  @variant hover, focus {
    background: var(--color-sky-600);
  }
}

Compiled CSS

.button {
  background: var(--color-sky-500);
  &:hover {
    @media (hover: hover) {
      background: var(--color-sky-600);
    }
  }

  &:focus {
    background: var(--color-sky-600);
  }
}

You're usually better off creating a real component and styling it with Tailwind CSS classes directly, but when CSS is the right tool, this makes @variant a lot nicer to work with.

Check out the @variant docs for more details.


Default values for functional utilities

Tailwind CSS v4.0 introduced functional @utility definitions, where you can use --value(…) and --modifier(…) to define utilities that accept theme values, bare values, arbitrary values, and modifiers.

In v4.3, you can now pass --default(…) to --value(…) and --modifier(…) when a utility should work with or without a value:

CSS

@utility tab-* {
  tab-size: --value(integer, --default(4));
}

Now the bare tab utility can fall back to 4, while tab-2 still resolves to 2:

HTML

<pre class="tab ...">...</pre>
<pre class="tab-2 ...">...</pre>

Generated CSS

.tab {
  tab-size: 4;
}

.tab-2 {
  tab-size: 2;
}

Check out the @utility docs for more details.

We just shipped a huge rebrand project, turning what was previously known as Tailwind UI into Tailwind Plus. Tailwind Plus is the all same high-quality resources you know from Tailwind UI, but with all-new possibilities and potential.

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. Today I'm super excited to share a new project that makes this constraint a thing of the past: a just-in-time compiler for Tailwind CSS.

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.

Headless UI is a set of completely unstyled, fully accessible UI components for React, Vue, and Alpine.js that make it easy to build fully accessible custom UI components, without sacrificing the ability to style them from scratch with simple utility classes.

v1.5.0

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.

New features

Component variants support

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:

<article class="prose md:prose-lg">
  <!-- Content -->

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.

Detecting errors in your CSS

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:

Catching conflicts in your HTML

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!

Quick fixes included

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:

Configuration

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.

Conclusion

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!

Want to talk about this post? Discuss this on GitHub →

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.


The job posting

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:

Read the job posting →

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.

The application process

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:

Read Robin's application →

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.

Filtering the applications

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.

The interview process

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.

Take-home project

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.

See the take-home project on GitHub →

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.

Pairing session

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.

Making the offer

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.

Want to talk about this post? Discuss this on GitHub →

v2.0

We just released Headless UI v2.0 for React which includes a ton of new stuff including built-in anchor positioning, a new headless checkbox component, HTML form components and more!

Last Checked
6h ago
Latest
4.3
Tracking since May 8, 2026