Tailwind Setup

Our general Tailwind setup involves a frontend.config.json and the usual tailwind.config.js file.

We use values from this JSON file inside the Tailwind config to separate Tailwind config itself from front end site data. This JSON file is often read by a project's Webpack set up, a project's JavaScript and a project's CMS. This set up is, of course, not mandatory to use these plugins, but they may still prove inspiration for your Tailwind set up all the same.

This documentation site is made using Tailwind and AREA 17 tailwind plugins, this page documents the config files used for this site.

tailwind.config.js

tailwind.config.js
// A17 tailwind plugins
const {
  Setup,
  ApplyColorVariables,
  BackgroundFill,
  ColorTokens,
  Components,
  Container,
  CssInJs,
  DevTools,
  FullBleedScroller,
  GridGap,
  GridLayout,
  GridLine,
  InteractionMediaQueries,
  Keyline,
  Layout,
  PseudoElements,
  RatioBox,
  Scrollbar,
  Spacing,
  SpacingTokens,
  StrokeFull,
  Typography,
  Underline,
} = require('../index');

// conf
const feConfig = require('./frontend.config.json');

module.exports = {
  content: ['./docs/**/*.html', './docs/*.html'],
  corePlugins: {
    container: false,
  },
  plugins: [
    Setup,
    ColorTokens,
    Components,
    BackgroundFill,
    Container,
    CssInJs,
    DevTools,
    FullBleedScroller,
    GridGap,
    GridLayout,
    GridLine,
    InteractionMediaQueries,
    Keyline,
    Layout,
    PseudoElements,
    RatioBox,
    Scrollbar,
    Spacing,
    StrokeFull,
    Typography,
    Underline,
  ],
  theme: {
    screens: feConfig.structure.breakpoints,
    mainColWidths: feConfig.structure.container,
    innerGutters: feConfig.structure.gutters.inner,
    outerGutters: feConfig.structure.gutters.outer,
    columnCount: feConfig.structure.columns,
    fontFamilies: feConfig.typography.families,
    typesets: feConfig.typography.typesets,
    spacingGroups: feConfig.spacing.groups,
    spacing: SpacingTokens(feConfig.spacing.tokens),
    colors: feConfig.color.tokens,
    borderColor: ApplyColorVariables(
      feConfig.color.tokens,
      feConfig.color.border
    ),
    textColor: ApplyColorVariables(feConfig.color.tokens, feConfig.color.text),
    backgroundColor: ApplyColorVariables(
      feConfig.color.tokens,
      feConfig.color.background
    ),
    underlineColor: ApplyColorVariables(
      feConfig.color.tokens,
      feConfig.color.underline
    ),
    scrollbarColor: {
      track: ApplyColorVariables(
        feConfig.color.tokens,
        feConfig.color.scrollbar.track
      ),
      thumb: ApplyColorVariables(
        feConfig.color.tokens,
        feConfig.color.scrollbar.thumb
      ),
    },
    ratios: feConfig.ratios,
    components: feConfig.components,
    css: feConfig.css,
    extend: {
      minHeight: ({ theme }) => theme('spacing'),
      maxWidth: ({ theme }) => theme('spacing'),
      spacing: {
        'safe-top': 'env(safe-area-inset-top)',
        'safe-bottom': 'env(safe-area-inset-bottom)',
        'safe-left': 'env(safe-area-inset-left)',
        'safe-right': 'env(safe-area-inset-right)',
        gutter: 'var(--inner-gutter)',
        'outer-gutter': 'var(--outer-gutter, 0px)',
      },
    },
  },
};

Breaking that down a little. Firstly require the plugins and then bring in the front end config file. Then set up Tailwind, JIT if you prefer.

As we have a custom container plugin, we disable the default Tailwind container class.

Then we bring in the plugins.

Then some we set the various TW theme items to their counterparts in the config JSON - with some examples like spacing and colours use plugins as functions to process values from the config JSON.

And finally, a few useful spacing extends.

frontend.config.json

Our config JSON is broken down into:

  • structure - here we describe the main layout of the site, its grid and composition, from these :root variables will be made
    • breakpoints - where each of our breakpoints starts
    • columns - how many design columns at each breakpoint
    • container - how wide the main container of the site is (auto or px/vw/rem)
  • gutters - gutters definitions
    • inner - between design columns
    • outer - on the outside of the main container
  • ratios - which ratios should be generated for ratio boxes
  • spacing - define the spacing values for use in Tailwind spacing classes like h-20 and mx-40
    • tokens - people rarely think in rems, they're abstract but as experienced screen developers we've been talking about pixels for years - we rename the spacing tokens to pixel values and use a function to convert them to rems and so mt-16 will give you a margin top of 16px (which in the output CSS will be 1rem)
    • groups - responsive spacing utility classes, create global spacing rules that are responsive and systematised, from these :root variables will be made
  • color - define site colours
    • tokens - these are the colour names, such as red-600, not super human readable, from these :root variables will be made
    • borderColor - apply colour tokens (or arbitrary colours) to human readable systematised named classes, eg: border-primary for your primary border, might map to primary: grey-5, from these :root variables will be made
    • textColor - same for text classes
    • backgroundColor - and background colours
  • typography - define the site type styles
    • families - firstly the families being used, from these :root variables will be made
    • typesets - responsive type sets, where breakpoint overrides can be specified and typography can be systematised
  • components - define any arbitrary components
  • css - define any arbitrary CSS

And so, in full:

frontend.config.json
{
  "structure": {
    "breakpoints": {
      "xs": "0",
      "sm": "544px",
      "md": "650px",
      "lg": "990px",
      "xl": "1300px",
      "xxl": "1520px"
    },
    "columns": {
      "xs": "4",
      "sm": "4",
      "md": "8",
      "lg": "12",
      "xl": "12",
      "xxl": "12"
    },
    "container": {
      "xs": "auto",
      "sm": "auto",
      "md": "auto",
      "lg": "auto",
      "xl": "auto",
      "xxl": "1440px"
    },
    "gutters": {
      "inner": {
        "xs": "10px",
        "sm": "15px",
        "md": "20px",
        "lg": "30px",
        "xl": "40px",
        "xxl": "40px"
      },
      "outer": {
        "xs": "20px",
        "sm": "30px",
        "md": "40px",
        "lg": "40px",
        "xl": "40px",
        "xxl": "0px"
      }
    }
  },
  "ratios": {
    "1x1": "1:1",
    "16x9": "16:9"
  },
  "spacing": {
    "tokens": {
      "scaler": 4,
      "arbitraries": {
        "400": "400px",
        "600": "600px"
      }
    },
    "groups": {
      "outer-1": {
        "xs": 64,
        "lg": 96
      },
      "inner-1": {
        "xs": 24,
        "md": 40,
        "lg": 64
      },
      "inner-2": {
        "xs": 16,
        "md": 24,
        "lg": 32
      }
    }
  },
  "color": {
    "tokens": {
      "white": "#fff",
      "grey-3": "#f8f8f8",
      "grey-5": "#f2f2f2",
      "grey-10": "#e6e6e6",
      "grey-15": "#d9d9d9",
      "grey-54": "#757575",
      "grey-90": "#1a1a1a",
      "black": "#000",
      "blue-01": "#0A152B",
      "blue-02": "#001F5C",
      "blue-03": "#004F91",
      "blue-04": "#313BFB",
      "blue-05": "#81EEF3",
      "blue-06": "#ADD8E6",
      "red-01": "#f00"
    },
    "border": {
      "primary": "black",
      "secondary": "grey-15",
      "tertiary": "grey-54",
      "code-example-filename": "blue-05"
    },
    "text": {
      "title": "black",
      "primary": "grey-90",
      "inverse": "white",
      "secondary": "grey-54",
      "accent": "blue-03",
      "code": "black",
      "code-example": "grey-3",
      "code-example-filename": "blue-05"
    },
    "background": {
      "primary": "white",
      "header": "grey-10",
      "footer": "grey-10",
      "banner": "grey-90",
      "accent": "blue-03",
      "column": "blue-05",
      "column-alt": "blue-04",
      "code": "grey-10",
      "code-example": "grey-90",
      "quote": "grey-5"
    },
    "underline": {
      "primary": "grey-54",
      "secondary": "blue-03"
    },
    "scrollbar": {
      "track": {
        "primary": "blue-06"
      },
      "thumb": {
        "primary": "blue-03"
      }
    }
  },
  "typography": {
    "families": {
      "sans": "SuisseIntl, Helvetica, Arial, sans-serif",
      "serif": "\"Times New Roman\", Georgia, serif",
      "mono": "\"Lucida Console\", Courier, monospace"
    },
    "typesets": {
      "h1": {
        "xs": {
          "font-family": "var(--sans)",
          "font-weight": "500",
          "bold-weight": "500",
          "font-size": "32px",
          "line-height": "1.2",
          "letter-spacing": "-0.02em",
          "font-smoothing": "true"
        },
        "md": {
          "font-size": "36px"
        },
        "lg": {
          "font-size": "48px"
        }
      },
      "h2": {
        "xs": {
          "font-family": "var(--sans)",
          "font-weight": "500",
          "bold-weight": "500",
          "font-size": "20px",
          "line-height": "1.2",
          "letter-spacing": "-0.02em",
          "font-smoothing": "true"
        },
        "md": {
          "font-size": "24px"
        },
        "lg": {
          "font-size": "28px"
        }
      },
      "h3": {
        "xs": {
          "font-family": "var(--sans)",
          "bold-weight": "500",
          "font-weight": "500",
          "font-size": "16px",
          "line-height": "1.2",
          "letter-spacing": "-0.02em",
          "font-smoothing": "true"
        },
        "md": {
          "font-size": "20px"
        }
      },
      "h4": {
        "xs": {
          "font-family": "var(--sans)",
          "bold-weight": "600",
          "font-weight": "600",
          "font-size": "14px",
          "line-height": "1.2",
          "letter-spacing": "-0.02em",
          "font-smoothing": "true"
        },
        "md": {
          "font-size": "16px"
        }
      },
      "body": {
        "xs": {
          "font-family": "var(--sans)",
          "bold-weight": "600",
          "font-size": "14px",
          "line-height": "1.7",
          "font-smoothing": "true"
        },
        "md": {
          "font-size": "16px"
        }
      },
      "ui": {
        "xs": {
          "font-family": "var(--sans)",
          "font-size": "12px",
          "line-height": 1.2,
          "font-weight": 400
        }
      },
      "code": {
        "xs": {
          "font-family": "var(--mono)",
          "font-size": "14px",
          "line-height": 1.2,
          "font-weight": 400
        }
      }
    }
  },
  "components": {
    ".show-grid": {
      "apply": "relative",
      "attributes": {
        "--grid-column-bg": "rgba(0, 0, 0, 0.05)"
      },
      "selectors": {
        "::after": "dev-tools-grid absolute w-full content-['']"
      }
    },
    ".blockquote": {
      "apply": "bg-quote py-20",
      "selectors": {
        "blockquote": "block px-20 md:px-40",
        "blockquote p": "italic",
        "blockquote p:first-child": "mt-0 relative",
        "blockquote p:first-child::before": "content-[open-quote] absolute top-0 right-full -left-8",
        "blockquote p:last-child::after": "content-[close-quote]",
        "figcaption": "block mt-8 px-20 md:px-40",
        "cite": "not-italic",
        "cite::before": "content-['—'] mr-8"
      }
    },
    ".code-example": {
      "apply": "block bg-code-example",
      "selectors": {
        ".code-example-filename": {
          "apply": "inline-block f-ui text-inverse px-20  py-12 border-b text-code-example-filename border-code-example-filename",
          "attributes": {
            "min-width": "160px"
          }
        },
        ".code-example-code": "block f-code bg-code-example text-code-example bg-code-example",
        ".code-example-code code": {
          "apply": "bg-code-example max-h-600 overflow-auto",
          "attributes": {
            "scrollbar-color": "var(--grey-54) var(--grey-90)",
            "scrollbar-width": "thin"
          },
          "selectors": {
            "::-webkit-scrollbar": {
              "attributes": {
                "width": "8px",
                "height": "8px"
              }
            },
            "::-webkit-scrollbar-track": {
              "attributes": {
                "background": "var(--grey-90)"
              }
            },
            "::-webkit-scrollbar-thumb": {
              "attributes": {
                "background": "var(--grey-54)",
                "width": "8px",
                "height": "8px"
              }
            }
          }
        }
      }
    },
    ".copy": {
      "breakpoints": {
        "md": {
          "selectors": {
            "> *:not(.code-example, h2, hr)": {
              "attributes": {
                "max-width": "80%"
              }
            }
          }
        },
        "lg": {
          "selectors": {
            "> *:not(.code-example, h2, hr)": {
              "attributes": {
                "max-width": "60%"
              }
            }
          }
        }
      },
      "selectors": {
        "h2": "f-h2",
        "h2:not(:first-child)": "mt-56 pt-48 border-t border-secondary",
        "h3": "f-h3 mt-40",
        "h4": "f-h4 mt-40",
        "p": "f-body mt-20",
        "p:first-child": "mt-0",
        "ul": "f-body mt-20 list-disc pl-16",
        "ul ul": "mt-0",
        ".code-example": "mt-20",
        "code:not([class])": "f-code text-code bg-code px-8 pt-4 pb-2",
        "pre:not([class])": "block mt-20 f-code max-h-400 overflow-y-scroll",
        "pre:not([class]) code:not([class])": "block p-8",
        "a": {
          "apply": "text-accent underline",
          "selectors": {
            ":hover, :focus": {
              "apply": "text-primary"
            }
          }
        },
        "hr": "border-secondary my-40",
        ".blockquote": "mt-20"
      }
    }
  }
}