How Tailwind CSS Uses PostCSS and Plugins to Generate Smart, Tree‑Shaken CSS
An in‑depth look at Tailwind CSS reveals how it harnesses PostCSS, JavaScript configuration, and custom plugins to generate and tree‑shake CSS, detailing classic @tailwind at‑rules, screen directives, and a sample z‑index plugin with code examples.
What is Tailwind CSS
If you open Tailwind CSS's GitHub repository you might be confused by the mix of CSS and JavaScript files and the unusual at‑rules. Tailwind leverages PostCSS at a higher level: a JavaScript configuration is parsed by
postcss‑jsand
postcss‑nested, producing a PostCSS AST that is then turned into CSS. Tools like PurgeCSS can further tree‑shake the generated CSS.
Classic Tailwind at‑rules
Three core at‑rules are used:
<code>@tailwind base;
@tailwind components;
@tailwind utilities;</code>These at‑rules trigger PostCSS plugins to inject the corresponding CSS fragments and fix source maps.
<code>css.walkAtRules('tailwind', atRule => {
if (atRule.params === 'preflight') {
// prettier-ignore
throw atRule.error("`@tailwind preflight` is not a valid at-rule in Tailwind v1.0, use `@tailwind base` instead.", { word: 'preflight' })
}
if (atRule.params === 'base') {
atRule.before(updateSource(pluginBase, atRule.source))
atRule.remove()
}
if (atRule.params === 'components') {
atRule.before(postcss.comment({ text: 'tailwind start components' }))
atRule.before(updateSource(pluginComponents, atRule.source))
atRule.after(postcss.comment({ text: 'tailwind end components' }))
atRule.remove()
}
if (atRule.params === 'utilities') {
atRule.before(postcss.comment({ text: 'tailwind start utilities' }))
atRule.before(updateSource(pluginUtilities, atRule.source))
atRule.after(postcss.comment({ text: 'tailwind end utilities' }))
atRule.remove()
}
if (atRule.params === 'screens') {
includesScreensExplicitly = true
atRule.before(postcss.comment({ text: 'tailwind start screens' }))
atRule.after(postcss.comment({ text: 'tailwind end screens' }))
}
})
if (!includesScreensExplicitly) {
css.append([
postcss.comment({ text: 'tailwind start screens' }),
postcss.atRule({ name: 'tailwind', params: 'screens' }),
postcss.comment({ text: 'tailwind end screens' }),
])
}</code>The helper below ensures newly generated nodes keep the original source information for correct sourcemaps:
<code>function updateSource(nodes, source) {
return _.tap(Array.isArray(nodes) ? postcss.root({ nodes }) : nodes, tree => {
tree.walk(node => (node.source = source))
})
}</code>Screen at‑rule example
Instead of writing a raw media query that duplicates that value like this:
<code>@media (min-width: 640px) {
/* ... */
}</code>you can use the @screen directive and reference the breakpoint by name:
<code>@screen sm {
/* ... */
}</code>The directive reads the breakpoint values from
tailwind.config.js(the
theme.screensobject) and generates the appropriate media query.
<code>import _ from 'lodash'
import buildMediaQuery from '../util/buildMediaQuery'
export default function({ theme }) {
return function(css) {
css.walkAtRules('screen', atRule => {
const screen = atRule.params
if (!_.has(theme.screens, screen)) {
throw atRule.error(`No `${screen}` screen found.`)
}
atRule.name = 'media'
atRule.params = buildMediaQuery(theme.screens[screen])
})
}
}</code>Writing Tailwind CSS plugins
Plugins generate initial CSS from JavaScript configuration and then let PostCSS perform a second pass. Below is a simplified z‑index plugin example.
<code>import _ from 'lodash'
import prefixNegativeModifiers from '../util/prefixNegativeModifiers'
export default function() {
return function({ addUtilities, e, theme, variants }) {
const utilities = _.fromPairs(
_.map(theme('zIndex'), (value, modifier) => {
return [
`.${e(prefixNegativeModifiers('z', modifier))}`,
{ 'z-index': value },
]
})
)
addUtilities(utilities, variants('zIndex'))
}
}</code>The call
addUtilities(utilities, variants('zIndex'))registers the generated utilities under
@tailwind utilities. The
@variants responsivewrapper then enables responsive variants, and a later PostCSS step (
substituteResponsiveAtRules) finalizes the CSS.
<code>expect(addedUtilities).toEqual([
{
utilities: {
'.-z-20': { 'z-index': '-20' },
'.-z-10': { 'z-index': '-10' },
'.z-10': { 'z-index': '10' },
'.z-20': { 'z-index': '20' },
},
variants: ['responsive'],
},
])</code>Final note
Tailwind CSS offers many more tricks; the documentation is worth exploring. The most striking insight is how PostCSS can be used to treat CSS as a programmable language, opening up a huge space for creative tooling.
37 Mobile Game Tech Team
37 Mobile Game Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.