Tips for your Tailwind Config

Tailwind CSS is an easy tool to pick up, but there are a lot of ways you can configure it. Here are some tips for a more transparent, streamlined and flexible config.

Tailwind 1.x has been out for nearly six months, and it made great strides in making an already useful tool more streamlined. But if you're someone who hasn't spent much time in the config, chances are you're still not getting the most out of Tailwind. Here are some tips for a more transparent, streamlined and flexible config.

Use the full config

We've made this case before, but it's worth revisiting with Tailwind 1.x, which shipped with a more thorough default configuration and an emphasis on extending, not replacing, these settings. The docs even mention:

"For most users we encourage you to keep your config file as minimal as possible, and only specify the things you want to customize."

While I appreciate the value in this for prototyping and projects where I’m designing in the browser, this is not a good idea if you are working in an established design system. When that is the case, it is crucial to generate a full config and strip away all irrelevant defaults.

You can start by copying an existing complete config or by creating a new one with the full flag:

npx tailwind init --full

Next, delete the defaults or copied values, especially those under colors, fontFamily, fontSize, maxWidth, and so on, so they can be replaced with custom values that match the current design. Of course some settings may stay fairly consistent across projects, like screens and spacing values, but even these should be re-evaluated. The result is a lean, tailored system with no bloat and a file that fully documents your configuration. This transparency is extra valuable if any one else ever needs to work on the project.

I give the Tailwind team a lot of credit for the care they’ve taken in putting together a base design system, but I’m even more thankful that they have preserved the tool’s full configurability.

Opt for Number Labels over Semantic Ones

Tailwind ships its default config with a heavy reliance on a semantic naming scheme. Text sizing, for example, uses a scale starting with xs and goes all the way up to 6xl. While there are plenty of advantages to this approach, they are all undone by the simple inflexibility of this pattern.

Javascript
fontSize: {
  xs: '12px',
  sm: '14px',
  base: '16px',
  lg: '18px',
  xl: '20px',
  '2xl': '24px',
  '3xl': '30px',
  '4xl': '36px',
  '5xl': '48px',
  '6xl': '64px',
}
Javascript
fontSize: {
  12: '12px',
  14: '14px',
  16: '16px',
  18: '18px',
  20: '20px',
  24: '24px',
  30: '30px',
  36: '36px',
  48: '48px',
  64: '64px',
}

Semantic labeling on the left; numerical labeling on the right. Pixels used instead of rems for simplicity.

One oft-cited benefit of divorcing the precise value from its name is that it’s easier to change every instance of, say, text-2xl by editing one line in the config. If this class were text-24, you’d need to edit each one to stay consistent with the new value.

However, this is solved with a quick find-and-replace. A more difficult problem arises when you need to wedge a value between xs and sm, or between 5xl and 6xl. This is especially an issue for max-width, which is much harder than text sizing to anticipate every need for ahead of time and spans a much wider range of values. When defining names with numbers, e.g. max-w-1280, you will never be unable to fit in a new class.

This system also eliminates the mental overhead of converting a value in Figma (or any other tool) to your Tailwind classes. Was 64px equivalent to text-5xl or text-6xl? Just use text-64. There are tools (see Tailwind CSS IntelliSense in a section below) for helping with this, or maybe you’re just that quick at internalizing the values, but it’s an unnecessary step and one that you shouldn’t force onto others who may be in the codebase.

This isn’t to say that semantic names are always bad — you definitely want to keep them for defining screens, and many properties vary much less, making them easier to label with a limited scale. For example, I often use the default naming schemes for line-height and letter-spacing.

Ultimately, there isn’t a correct pattern for any one property, but be sure to ask yourself if you would benefit from the flexibility of using numbers in the labels.

Spread on

Spreading is an ES6 syntax with that lets you expand iterables, and is useful in Tailwind as you can use it to combine objects. If it’s unfamiliar to you, you can see it happening in the default config here:

margin: (theme, { negative }) => ({
  auto: 'auto',
  ...theme('spacing'),
  ...negative(theme('spacing')),
})

This combines objects returned from the theme() and negative() calls with the auto entry to create one large object for margin. These are two functions that ship with Tailwind to cut down on verbosity, and there’s no reason we can’t add our own.

If you’re following the tips so far, you might be thinking that you have an awfully large config. While the comprehensiveness is a good thing, we are repeating ourselves when using pixels and forcing math upon us when adding new em or rem values. For instance:

screens: {
  sm: '40em',
  md: '48em',
  lg: '64em',
  xl: '80em',
},
borderWidth: {
  2: '2px',
  3: '3px',
  5: '5px',
},
fontSize: {
  12: '0.75rem',
  13: '0.8125rem',
}

All of these values are either hard to read or redundant. But if we define a few functions to abstract away both of these issues, we could instead write something like:

screens: {
  sm: em(640),
  md: em(768),
  lg: em(1024),
  xl: em(1280),
},
borderWidth: {
  ...px(2),
  ...px(3),
  ...px(5),
},
fontSize: {
  ...rem(12),
  ...rem(13),
}

This makes it easier to parse as well as easier to add values. The functions used above look like:

const em = px => `${px / 16}em`
const rem = px => ({ [px]: `${px / 16}rem` })
const px = num => ({ [num]: `${num}px` })

Manipulating Colors

Despite needing to add an alpha channel to at least one color on every project, I’ve always found myself using the same workflow: Switch to the browser, Google the hex value, grab the converted RGB value, switch back to the editor, paste and edit it into an rgba(). This works just fine, but if you’d like to avoid this manual conversion, there are other options. Each of these has its pros and cons, so go with your personal preference.

With JS: A little over the top

Since we’re working in a JS file, you can easily import a JS color manipulation library and use it on your hex value. Using TinyColor, for example, you might make a 50% transparent version of a color like this:

const tinycolor = require('tinycolor2')

module.exports = {
  // [...other tailwind code]
  theme: {
    colors: {
      'purple': '#9f7aea',
      'purple-50': tinycolor('#9f7aea').setAlpha(0.5).toRgbString(),
    }
  }
}

I find this to be the most verbose option, however.

With PostCSS Color Functions: Simple but deprecated

If you are using Tailwind with PostCSS, you can make use of the Color Function plugin . As long as you are running the color plugin after the Tailwind build step in your PostCSS configuration, you can include a value in your Tailwind config that will later get transformed.

module.exports = {
  // [...other tailwind code]
  theme: {
    colors: {
      'purple': '#9f7aea',
      'purple-50': 'color(#9f7aea a(50%))',
    }
  }
}

This function has a super simple syntax for adding transparency to a hex value, but it is unfortunately one that is no longer planned to become part of the CSS spec. So while you can certainly use this plugin to correctly transform the value, you’ll probably never be able to eliminate the dependency on PostCSS.

With PostCSS Alpha Hex Colors: Clunkier but future-proof

Alpha hex colors are part of the new CSS spec, and work by adding an alpha channel to the end of a hex value. For three-digit hex values, like #fff, this is a single value: #fffc. For six-digits, two values are added: #ffffffcc. Unfortunately, these alpha values must be written in hexadecimal, so something like 75% would actually be written as bf. To solve this, I’m including an object mapping every value between 0-100 at the top of my file, so I can write:

const a = { 100: 'ff', 99: 'fc', 98: 'fa', 97: 'f7', 96: 'f5', 95: 'f2', 94: 'f0', 93: 'ed', 92: 'eb', 91: 'e8', 90: 'e6', 89: 'e3', 88: 'e0', 87: 'de', 86: 'db', 85: 'd9', 84: 'd6', 83: 'd4', 82: 'd1', 81: 'cf', 80: 'cc', 79: 'c9', 78: 'c7', 77: 'c4', 76: 'c2', 75: 'bf', 74: 'bd', 73: 'ba', 72: 'b8', 71: 'b5', 70: 'b3', 69: 'b0', 68: 'ad', 67: 'ab', 66: 'a8', 65: 'a6', 64: 'a3', 63: 'a1', 62: '9e', 61: '9c', 60: '99', 59: '96', 58: '94', 57: '91', 56: '8f', 55: '8c', 54: '8a', 53: '87', 52: '85', 51: '82', 50: '80', 49: '7d', 48: '7a', 47: '78', 46: '75', 45: '73', 44: '70', 43: '6e', 42: '6b', 41: '69', 40: '66', 39: '63', 38: '61', 37: '5e', 36: '5c', 35: '59', 34: '57', 33: '54', 32: '52', 31: '4f', 30: '4d', 29: '4a', 28: '47', 27: '45', 26: '42', 25: '40', 24: '3d', 23: '3b', 22: '38', 21: '36', 20: '33', 19: '30', 18: '2e', 17: '2b', 16: '29', 15: '26', 14: '24', 13: '21', 12: '1f', 11: '1c', 10: '1a', 9: '17', 8: '14', 7: '12', 6: '0f', 5: '0d', 4: '0a', 3: '08', 2: '05', 1: '03', 0: '00'}

module.exports = {
  [...other tailwind code]
  theme: {
    colors: {
      'purple': '#9f7aea',
      'purple-50': `#9f7aea${a[50]}`,
    }
  }
}

Since this is not deprecated, you will already have support for alpha hex colors if you are using postcss- preset-env (with default settings), and you’re following the spirit of using future syntax.

Screens

Thinking and coding mobile-first is a paradigm that has endured, but it by no means disqualifies the use of max-width media queries. If you need to hide something on smaller screens, for instance, you’ll be writing more code if you only use min-width media queries.

<div class=“hidden lg:block”>

Including inverse queries of your screens lets you solve this. Here, I’m adding a d for “down.”

screens: {
  tyd: { max: em(399) },
  ty: em(400),
  xsd: { max: em(599) },
  xs: em(600),
  smd: { max: em(767) },
  sm: em(768),
  mdd: { max: em(959) },
  md: em(960),
  lgd: { max: em(1023) },
  lg: em(1024),
  xld: { max: em(1279) },
  xl: em(1280),
}

Allowing us to use a single class:

<div class="lgd:hidden">

It’s worth noting that adding entries to your screens is a main contributor to Tailwind’s build size; you should be using Purgecss to trim your build to what’s being used.

Tailwind CSS IntelliSense

Alright, this isn’t necessarily a configuration tip but if you are using VS Code and not using the Tailwind CSS IntelliSense plugin, you are missing out. This is an irreplaceable tool for getting config suggestions as you are writing in markup or stylesheets, and is especially valuable when switching between Tailwind projects with different configs.

Conclusion

Tailwind is a powerful tool for creating an extensive yet organized utility class system, and hopefully these tips make your Tailwind experience even better. For more of our thoughts on Tailwind, check out our best practices and advice for problems you may run into when combining it with Purgecss.

Greg Kohn

Greg is a front-end developer who loves developing quick, responsive sites that are as elegant in a text editor as they are in the browser. He works in our Falls Church, VA, HQ for clients including U.S. News and World Report.

More articles by Greg