adamwathan ยท GitHub

This PR changes some decisions about the config file structure that we never would have made had we known that eventually we would be adding the modules key and the options key.

TL;DR: Some sections have moved or been renamed, and we are going to start encouraging people to keep their config files much simpler and inherit our defaults unless they explicitly want to override or extend them.

Many thanks to @reinink, @samselikoff, @taylorotwell, @derrickreimer, @davidhemphill, and @jasonmccreary for putting up with my incessant badgering, whining, desperate requests for feedback and border-line depression trying to make this not suck over the last three weeks. You don't even want to see all of the iterations this went through ๐Ÿ˜ฉ

  1. Options are now top-level.

    The options key as been removed and the separator, prefix, and important options are now top-level keys in the config file:

    module.exports = {
      prefix: '',
      important: false,
      separator: ':',
      // ...
    }

    This is part of a larger vision where the config file is truly more of a config file, and details about your design system are just a specific part of that rather than the top-level focus. Think of it more like a postcss.config.js or a babel.config.js file โ€” a bunch of options for configuring Tailwind the tool, where your styles are just one part of that.

  2. Style-related options have moved to a new theme section.

    All of the top-level options related to your design system (screens, colors, fonts, backgroundColors, padding, margin, etc) have been nested inside a new theme section.

    After introducing the modules, options, and plugins sections, it felt very off to have all of the style options be direct siblings of options that were much more configuration-ish.

    It also made things look a bit weird if you (like many) kept your style-related configuration in other files, because you need to spread your styles into the configuration at the top-level:

    module.exports = {
      ...styles,
      modules: [
        // ...
      ],
      plugins: [
        // ...
      ],
      options: {
        prefix: '',
        important: false,
        separator: ':',
      }
    }

    With everything nested underneath the new theme section, the example above would look like this:

    module.exports = {
      prefix: '',
      important: false,
      separator: ':',
      theme: styles,
      modules: [
        // ...
      ],
      plugins: [
        // ...
      ],
    }

    Not a huge difference, but conceptually I think it makes a lot more sense for those options to be grouped together instead of all sitting at the top-level alongside things like options and modules.

  3. Your theme section is merged with the default theme values.

    Tailwind already works this way in 0.x, but it's worth noting here for clarity.

    You can leave your theme section completely empty and your build will just use Tailwind's default values.

    If you provide any keys, those keys will override the default values.

    module.exports = {
      theme: {
        // This will completely replace the default tracking values
        tracking: {
          '1': '-0.05em',
          '2': '0',
          '3': '0.05em',
        },
      },
    }

    To extend any of Tailwind's defaults, you can grab the default values from tailwindcss/defaultTheme and spread them in:

    const defaultTheme = require('tailwindcss/defaultTheme')()
    module.exports = {
      theme: {
        // This will extend the default tracking values
        tracking: {
          ...defaultTheme.tracking,
          wider: '0.1em',
          widest: '0.15em',
        },
      },
    }

    Note that the tailwindcss/defaultTheme module exports a function that returns the default theme (to avoid accidental mutation).

    Based on a lot of user interviews I've done over the last few weeks and a poll I conducted on Twitter, I learned that a lot of people really value having Tailwind provide well-considered default values.

    So for v1.0, we are going to take a bit of a more opinionated stance and encourage partial customization of the default config by just overriding and extending the parts you want to change instead of publishing the entire default config by default for you to own and manipulate.

    Everything of course will still be as customizable as ever, but the hope is that most people's config files will end up being much shorter/simpler, and serve just as a reference for the user's customizations, rather than having all of the defaults and their customizations combined into one giant file, where it's hard to tell what came with Tailwind out of the box vs. what was added by the user.

  4. The modules section has been renamed to variants.

    "Modules" was a word we just kinda grabbed because we needed something, and we wanted to use that section of the config to both specify variants and disable modules if necessary.

    Now that all of Tailwind's internal "modules" are actually just core plugins, I want to deprecate this terminology entirely, and make this section of the config purely about configuring variants for core plugins.

      module.exports = {
        // ...
    -   modules: {
    +   variants: {
          appearance: ['responsive'],
          backgroundAttachment: ['responsive'],
          backgroundColors: ['responsive', 'hover', 'focus'],
          // ...
          zIndex: ['responsive'],
        },
        // ...
      }
  5. Your variants section is merged with the default variants values.

    Just like the theme section, you only need to provide values for the variants you'd like to override.

    If you are fine with the defaults, you don't even need to provide the key.

    To add hover variants to opacity for example, you'd just add the opacity key and list the variants you want to generate:

    module.exports = {
      // ...
      variants: {
        opacity: ['responsive', 'hover'],
      },
      // ...
    }

    It's important to note that whatever you provide overrides the default value, it's not merged. So to add a hover variant to opacity, you need to re-specify the responsive variant too, unless of course you don't want to generate it.

    We may make it easier to simply extend this configuration later, but I don't think this will be a real pain point for anyone in practice.

  6. Modules are disabled in the corePlugins section, not the variants section.

    Instead of setting a module core plugin to false in the variants section of your config to disable it, you now set that plugin to false in a new corePlugins section of the config:

      variants: {
        // ...
        negativeMargin: ['responsive'],
    -   objectFit: false,
    -   objectPosition: false,
        opacity: ['responsive'],
        // ...
      },
    + corePlugins: {
    +   objectFit: false,
    +   objectPosition: false,
    + },

    The motivation behind this is that in the near future, I plan to rewrite our preflight styles as a core plugin as well, and if users want to disable preflight, doing so in the variants section of the config makes no sense because there's no way to add "responsive" or "hover" versions of preflight.

    We may also move the container plugin back to being a true "core plugin" instead of just a third-party plugin that happens to be in the same repository, so this change is also important in order for people to disable that plugin if we make that change.

    Overall, it just makes sense that disabling core plugins is divorced from variant configuration, because the idea of variants isn't relevant for every core plugin.

  7. Core plugins can be configured directly through the corePlugins section.

    In Convert built-in utility modules to private plugins #620, all of Tailwind's built-in modules were rewritten to use the plugin system, so there is no difference between the code we write to add new core functionality to Tailwind and the code that someone might write when authoring a third-party plugin.

    Part of this included each plugin becoming directly configurable (for Tailwind's benefit internally), much like a third-party plugin would be.

    Essentially, there's no reason you couldn't disable a core plugin, then re-introduce it by adding it your plugins key and passing it the appropriate configuration:

    corePlugins: {
      backgroundSize: false,
    },
    plugins: [
      require('tailwindcss/plugins/backgroundSize')({
        variants: ['responsive'],
        values: {
          auto: 'auto',
          cover: 'cover',
          contain: 'contain',
        },
      })
    ]

    In addition to allowing you to disable core plugins, the corePlugins key also allows you to completely override a core plugin's configuration, by providing a configuration object for that plugin.

    This example does exactly the same thing as the previous example:

    corePlugins: {
      backgroundSize: {
        variants: ['responsive'],
        values: {
          auto: 'auto',
          cover: 'cover',
          contain: 'contain',
        },
      },
    },

    There's essentially no reason at all to do this currently (it's much simpler to configure a core plugin through the variants and theme keys), but designing the configuration file this way means it could be possible to pass advanced configuration options to core plugins in the future that might not be exposed through the theme section.

    For example, perhaps the margin plugin could expose an additional option to let you customize the class names to margin-{side}-{size} instead of the default m{size}-{size} syntax:

    corePlugins: {
      margin: {
        variants: ['responsive'],
        values: {
          auto: 'auto',
          // ...
          '32': '8rem',
        },
        classPrefix: 'margin-',
      },
    },

    I don't have concrete plans to introduce features like this to the core plugins, but designing the config file this way at least makes it possible.

  8. Every section is optional, as is the entire config file.

    As I alluded to in point 3, one of the main themes for Tailwind v1.0 is going to be doubling down on the defaults and shouldering more of the burden for making sure the default values are awesome.

    Until now, it's been easy to tell people "just customize it in your config" if they weren't happy with a default value. Going forward, I'd like to try even harder to make the defaults as widely usable as possible so that customization because more of an escape hatch than a mandatory step in using Tailwind.

    As I mentioned before, there are no plans to make it harder or impossible to customize any of the things you can currently customize โ€” being able to customize things is the whole reason I created the framework! But rather than sending the message of "you own every value the framework uses, customize the hell out of it", I want to start saying "we've worked really hard to give you the best possible starting point we could create, but if you need to change or extend the framework in any way, it's easy to do".

    Certain things are going to be very commonly completely replaced, like colors and fonts. But we should be able to make most of the other stuff suitable for just about any project right out of the box. Sure you might need to add an extra margin or padding value here and there, but in general you should be able to rely on our defaults and get pretty far.

    All that to say, in this config file, every single key is optional. You don't even need to provide a config file at all if you don't want:

    // postcss.config.js
    module.exports = {
      plugins: [
        // Pass a custom config file if you have one:
        require('tailwindcss')('./tailwind.js'),
        // Or pass nothing and just use the defaults:
        require('tailwindcss')(),
      ]
    }

    All of this is already true in Tailwind 0.x for what it's worth, but most people don't use Tailwind this way because it's not the path we encourage.

    So in a future PR, the tailwind init command will be changing as well, to create a much more minimal config file by default, with perhaps an extra flag you can pass if you want to generate the entire thing (like a tailwind init --eject or something) and not inherit the default values at all.

Read the original on github.com โ†—