Working with Tailwind
In this section, we are going to dive inside the tailwind.config.js
file located at the root of the project. Everything happens there, so we need to give you all the info you'll need to be able to customize it for your needs.
Configuration imports
The tailwind configuration file starts with commonjs (using require
instead of import
) import statements:
const colors = require("tailwindcss/colors");
const plugin = require("tailwindcss/plugin");
We are doing 2 things:
- We first load the
colors
object from tailwind to be able to access the native colors later in the configuration file - We then load the
plugin
object to be able to use tailwind plugins later in the file.
Dark mode & Content
We then take care of configuring the Dark mode as well as the files to parse, looking for tailwind class names to compile in the final bundle:
module.exports = {
darkMode: "class",
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
// ...rest of config
};
We are doing 2 things:
- We are first telling tailwind to use the
class
implementation of the dark mode. This means that when the dark mode is toggled, a.dark
class is added to thehtml
DOM element, affecting all the application styles and colors. See Tailwind docs for more details about dark mode.
<!--Dark mode disabled-->
<html></html>
<!--Dark mode enabled-->
<html class="dark"></html>
- We then tell tailwind to look for classes to compile inside all files residing inside the project folders, including files ending with different extensions. Learn more about content parsing by taking a look at the Tailwind docs.
Extending the base theme
The tailwind configuration file provides a very convenient way of overwriting the default theme and set your own variables. Everything related to theme customization goes inside the extend theme section of the configuration file:
module.exports = {
// ...previous config elements
theme: {
extend: {
// The theme configuration goes here.
},
},
// ...rest of config
};
There are several things we can change in there. If you want more details about the tailwind configuration, you can always read the dedicated section of the Tailwind docs. Let's get back to Apollo and deep dive in each subsection of the theme configuration.
Breakpoints
In Tailwind CSS, responsive breakpoints are known as screens
. Being a mobile first framework, screens are set in an ascending manner, so you always design mobile first. Here is the default Tailwind configuration:
module.exports = {
// ...previous config elements
theme: {
extend: {
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
xxl: "1536px",
},
},
},
// ...rest of config
};
While the defaults are pretty satisfying, we though that we could tweak them a little bit to get the most out of it, and to be able to face those very particular edge cases that come up from time to time. Here is Apollo's screens configuration:
module.exports = {
// ...previous config elements
theme: {
extend: {
screens: {
xs: { max: "639px" },
sm: "640px",
md: "768px",
lg: "1025px",
xl: "1280px",
xxl: "1536px",
ptablet: {
raw: "(min-width: 768px) and (max-width: 1024px) and (orientation: portrait)",
},
ltablet: {
raw: "(min-width: 768px) and (max-width: 1024px) and (orientation: landscape)",
},
},
},
},
// ...rest of config
};
Let's go through the changes we've made. We've added an xs
screen that, opposed to the others that follow an ascending pattern, is capped to 639px
to face some very rare edge cases where you'd like to have something only applied on very small screens. We've also decided to push the lg
breakpoint to 1025px
instead of 1024px
to exclude landscape tablets from desktop display, simply because there is not as much room on a landscape tablet as there is on a desktop screen, implying therefore layout adjustments between the two. And finally, we've added two raw screens called ptblet
(stands for portrait tablet) and ltablet
(stands for landscape tablet) to handle specific layouts rendered in both orientations at maximum design quality.
Colors
Tailwind CSS provides a very advanced and fully customizable color system. If you don't know anything about Tailwind colors, we recommend that you take a look at . the Tailwind docs to learn more about them. In Apollo, we use the theme section of the configuration file to configure our own colors, that we will be using in the template. It also means, that you can do the same thing to change all the template colors to match your branding. Here is the color configuration of Apollo:
module.exports = {
// ...previous config elements
theme: {
extend: {
colors: {
muted: colors.slate,
primary: colors.violet,
info: colors.sky,
success: colors.teal,
warning: colors.amber,
danger: colors.rose,
},
},
},
// ...rest of config
};
Tailwind CSS provides 22 pre-built colors, each one having 10 different shades, from the lightest one to the darkest one. Let's talk about some of our choices. First, even if the system is pretty complete, we felt it needed an additional really darker shade to enhance dark mode renders. Therefore we decided to add an additional shade to the gray based Tailwind colors.
WARNING
Consider the slate
, gray
, zinc
, neutral
and stone
colors like base grey-ish backgrounds and tints for your design system and UI elements. Avoid at all costs mixing them as it will affect the consistency of your color system. Instead use one of those consistently throughout your design.
Following the gold rule in the above warning, we've decided to abstract the gray color we want to use for the template inside the muted
variable.
muted: colors.slate,
That simply is a shortcut to use our slate color through the template without taking the risk of mixing with another tint of gray. Therefore, instead of doing:
<span class="text-slate-600"></span>
We now simply write:
<span class="text-muted-600"></span>
Yeah, we pretty much know what you're going to say: "Hold on, this is pretty and nice, but how is it going to help me with my development? What benefit is this bringing?".
- First, you don't take the risk of mixing inconsistent gray shades, having
gray
in some parts,slate
in others, and maybestone
somewhere else. The main gray shade is abstracted in themuted
color variable and prevents any potential errors, like when multiple people work on the same project. - Second, if you want to change the overall look of your app, you can do it anytime. You just need to switch the color linked to the
muted
variable, like this:
// If you want a slate dominant in your layout
muted: colors.slate,
// If you want a gray dominant in your layout
muted: colors.gray,
// If you want a zinc dominant in your layout
muted: colors.zinc,
// If you want a neutral dominant in your layout
muted: colors.neutral,
// If you want a stone dominant in your layout
muted: colors.stone,
we use the same mechanism to abstract other colors that you might need in a web project, like a primary
color or other colors like success
and danger
that symbolize some application states. You can configure those colors as shortcuts as well:
//The template primary color
primary: colors.indigo,
//The blue-ish info state
info: colors.sky,
//The green-ish success state
success: colors.teal,
//The orange-ish warning state
warning: colors.amber,
//The red-ish danger state
danger: colors.rose,
Therefore, instead of using the indigo color, we abstract it to primary:
<!--You shouldn't do that-->
<span class="text-indigo-600"></span>
<!--This is the right way-->
<span class="text-primary-600"></span>
You should now have a good understanding of how colors work in Apollo. Take a look at how we use them inside the template to get a more precise idea of the possibilities.
Fonts
Tailwind CSS provides a very simple way to abstract your fonts, like we did with colors. If you don't know anything about Tailwind font families, we recommend that you take a look at the Tailwind docs to learn more about how to configure and use them. In Apollo, we use the theme section of the configuration file to configure our own fonts. It also means, that you can do the same thing to change all the template fonts to match your branding. Here is the color configuration of Apollo:
module.exports = {
// ...previous config elements
theme: {
extend: {
fontFamily: {
sans: ["Roboto flex", "sans-serif"],
heading: ["Inter", "sans-serif"],
},
},
},
// ...rest of config
};
We are doing 2 things:
- We are first telling tailwind to overwrite its base
sans
font stack and to useRoboto flex
instead. You then simply call this font in your templates by using thefont-sans
class expression. - We then add a new utility for headings that is using the
Inter
font. You then call this font in your templates by using thefont-heading
class expression.
You could also follow the same pattern if you want to customize the native font-serif
and font-mono
stacks that Tailwind provides by default. You could add something like this in the configuration above:
module.exports = {
// ...previous config elements
theme: {
extend: {
fontFamily: {
serif: ["Some Font Name", "serif"],
mono: ["Some Font Name", "monospace"],
},
},
},
// ...rest of config
};
plugins
Like we saw in the very first section of this page, we are first importing the tailwind plugin object to be able to use plugins later in our configuration file.
const plugin = require("tailwindcss/plugin");
We can then use this plugin definition to actually load our plugins into Tailwind CSS. Keep in mind that those plugins need to be installed via pnpm
or it will result in an error when running the project.
module.exports = {
// ...previous config elements
theme: {
extend: {
plugins: [
require("@tailwindcss/typography"),
require("@tailwindcss/aspect-ratio"),
],
},
},
// ...rest of config
};
In the sample above, we are loading the typography
plugin, which we saw earlier, as well as the line-clamp
, aspect ratio
and @vidstack
plugins.
- The typography plugin is in charge of styling user generated HTML or markdown content. You can read more about it in the official Tailwind documentaion.
- The aspect-ratio plugin is in charge of rendering correct aspect ratios like
1:1
,4:3
or16:9
. You can read more about it in the official Tailwind documentation.
Adding plugin utilities
we can also use the plugin object to add flat utilities directly in the configuration file:
module.exports = {
// ...previous config elements
theme: {
extend: {
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
".slimscroll::-webkit-scrollbar": {
width: "6px",
},
".slimscroll::-webkit-scrollbar-thumb": {
borderRadius: ".75rem",
background: "rgba(0, 0, 0, 0.1)",
},
".slimscroll-opaque::-webkit-scrollbar-thumb": {
background: "rgba(0, 0, 0, 0) !important",
},
".mask": {
"mask-size": "contain",
"mask-repeat": "no-repeat",
"mask-position": "center",
},
".mask-blob": {
"mask-image":
"url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBDMjAgMCAwIDIwIDAgMTAwczIwIDEwMCAxMDAgMTAwIDEwMC0yMCAxMDAtMTAwUzE4MCAwIDEwMCAweiIvPjwvc3ZnPg==')",
},
});
}),
],
},
},
// ...rest of config
};
The classes added above will are available everywhere in the project and are also compatible with the Tailwind responsive-variants
that rely on the breakpoints you've defined earlier.
Expose Tailwind colors as CSS variables
we can also use the plugin object to expose Tailwind colors as native CSS variables from the configuration file:
module.exports = {
// ...previous config elements
theme: {
extend: {
plugins: [
function ({ addBase, theme }) {
function extractColorVars(colorObj, colorGroup = "") {
return Object.keys(colorObj).reduce((vars, colorKey) => {
const value = colorObj[colorKey];
const newVars =
typeof value === "string"
? { [`--color${colorGroup}-${colorKey}`]: value }
: extractColorVars(value, `-${colorKey}`);
return { ...vars, ...newVars };
}, {});
}
addBase({
":root": extractColorVars(theme("colors")),
});
},
],
},
},
// ...rest of config
};
Tailwind colors are now available anywhere. For example, to get the bg-indigo-500
tailwind color, you simply need to reference a CSS variable like this: var(--color-indigo-500)
. That's it.
Complete configuration
That's it! Now that we've been through the entire Tailwind configuration file, you should have a solid understanding of what's happening in there, how to make changes to adapt it to your context and to get the most out of it. Here a complete configuration example:
/** @type {import('tailwindcss').Config} */
const colors = require('tailwindcss/colors')
const plugin = require('tailwindcss/plugin')
module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
darkMode: 'class',
theme: {
extend: {
screens: {
xs: { max: '639px' },
sm: '640px',
md: '768px',
lg: '1025px',
xl: '1280px',
xxl: '1536px',
ptablet: {
raw: '(min-width: 768px) and (max-width: 1024px) and (orientation: portrait)',
},
ltablet: {
raw: '(min-width: 768px) and (max-width: 1024px) and (orientation: landscape)',
},
},
colors: {
muted: colors.slate,
primary: colors.violet,
info: colors.sky,
success: colors.teal,
warning: colors.amber,
danger: colors.rose,
},
fontFamily: {
sans: ['Roboto Flex', 'sans-serif'],
heading: ['Inter', 'sans-serif'],
},
},
},
plugins: [
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
plugin(function ({ addUtilities }) {
addUtilities({
'.slimscroll::-webkit-scrollbar': {
width: '6px',
},
'.slimscroll::-webkit-scrollbar-thumb': {
borderRadius: '.75rem',
background: 'rgba(0, 0, 0, 0.1)',
},
'.slimscroll-opaque::-webkit-scrollbar-thumb': {
background: 'rgba(0, 0, 0, 0) !important',
},
'.mask': {
'mask-size': 'contain',
'mask-repeat': 'no-repeat',
'mask-position': 'center',
},
'.mask-blob': {
'mask-image':
"url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTAwIDBDMjAgMCAwIDIwIDAgMTAwczIwIDEwMCAxMDAgMTAwIDEwMC0yMCAxMDAtMTAwUzE4MCAwIDEwMCAweiIvPjwvc3ZnPg==')",
},
})
}),
function ({ addBase, theme }) {
function extractColorVars(colorObj, colorGroup = '') {
return Object.keys(colorObj).reduce((vars, colorKey) => {
const value = colorObj[colorKey]
const newVars =
typeof value === 'string'
? { [`--color${colorGroup}-${colorKey}`]: value }
: extractColorVars(value, `-${colorKey}`)
return { ...vars, ...newVars }
}, {})
}
addBase({
':root': extractColorVars(theme('colors')),
})
},
],
}