How to Work With CSS in Astro
There are various ways to style components in Astro. We can write CSS directly in Astro components, import them from external files, or use CSS libraries such as Tailwind or Sass. In this tutorial, we'll explore different ways to use CSS within Astro.
Types of CSS
There are five main ways to write CSS in Astro: scoped, global, inline, conditional CSS, and using server-side CSS variables for dynamic behavior. Let's go through each one to see how they work.
Scoped CSS
The most common way to style Astro components is to use scoped CSS. As mentioned in a previous tutorial, Astro components can use style
tags. When writing CSS in between these tags inside a component, the styles will be scoped. This means styles created for the component won't be accessible anywhere else, preventing style collisions. Consider the following example:
In this example, the color
will be scoped to the Heading
component. Using an h1
elsewhere won't receive this style. We can use a low-specificity tag selector, and Astro will automatically assign a unique selector to the element.
Specificity levels are preserved, so using a class
or id
selector over the above example will override the styles. Take a look at the generated output in the example below:
<h1 class="heading">
<slot />
</h1>
<style>
h1 {
color: #2980b9;
}
.heading {
color: red;
}
</style>
<!-- The component will generate the following tag: -->
<h1 class="heading" data-astro-cid-j7pv25f6 />
The .heading
class will override the tag selector, resulting in a different style for the h1
. The class
is also preserved in the generated output, but it'll be scoped to the unique selector. The final selector based on the above example will be:
.heading[data-astro-cid-j7pv25f6] { ... }
Global CSS
The opposite of scoped CSS is global CSS, where styles are shared across components and available globally. We can create global CSS in four different ways:
<!-- 1οΈβ£: Using the `:global` pseudo selector -->
<style>
:global(h1) { color: #2980b9; }
</style>
<!-- 2οΈβ£: Using the `is:global` directive -->
<style is:global>
h1 { color: #2980b9; }
</style>
<!-- 3οΈβ£: Using a CSS import inside a layout -->
---
import '../css/global.css'
---
<!-- 4οΈβ£: Using a `link` tag (CSS must be placed in `public` dir) -->
<link rel="stylesheet" href="./assets/css/styles.css" />
- Using the
:global
pseudo selector. - Using the
is:global
directive to make all styles within astyle
tag global. - Using a CSS import inside a layout.
- Using a
link
tag that imports CSS from thepublic
folder.
All of the above options will create global CSS rules, exposing the styles to all elements. Note that the first option will only make a single selector global, while the second option makes all selectors inside the style
tag global. This directive is specific to Astro. It's also possible to use both <style>
and <style is:global>
in the same component:
<h1>
<slot />
</h1>
<!-- Scoped CSS: -->
<style>
h1 {
color: #2980b9;
}
</style>
<!-- Global CSS: -->
<style is:global>
h1 {
margin-top: 20px;
}
</style>
This will keep the color
scoped for the component but make the margins globally available. Note that the third and fourth options only work with a layout that is reused across pages. For example:
// Inside a Layout.astro component
---
import '../css/global.css'
---
<html lang="en">
<head>...</head>
<body>
<slot />
</body>
</html>
// Reusing the component on different pages:
---
import Layout from '../components/Layout.astro'
---
<Layout>
<h1>Welcome to Astro</h1>
</Layout>
For the fourth option, we also need to place the CSS into the public
folder. The public
folder is not bundled or processed by Astro, so CSS files used here will be left untouched.
So which one should you use? In terms of readability and maintainability, the best option is to import CSS into a layout. If you need a limited number of global CSS and you'd like to keep the rules next to the layout, then using the is:global
directive is the second-best option.
Inline CSS
Although it's not recommended for most use cases, Astro also supports the use of inline CSS. Both JSX and HTML syntax are supported:
<h1 style={{ color: '#2980b9' }}>JSX syntax</h1>
<h1 style="color:#2980b9;">HTML syntax</h1>
As inline CSS has the highest specificity, it can override other CSS rules. This can have unexpected consequences. Because of this, only use inline CSS as a last resort. For example, a common use case for inline CSS is using it for dynamically generated styles through JavaScript. Scroll animations also often use inline CSS.
Inlining stylesheets
It's also possible to inline entire stylesheets into documents by configuring Astro's build process. It can be achieved by setting the inlineStylesheets
property in the build
node:
import { defineConfig } from 'astro/config'
export default defineConfig({
build: {
assetsInlineLimit: 1024 // Link styles externally above 1024 bytes
inlineStylesheets: 'always' // Can also be set to 'never'
}
})
This can be useful if you want to inline critical CSS to avoid render blocking and improve a site's loading performance. It's also possible to configure when stylesheets should be inlined or loaded externally using the assetsInlineLimit
option.
Note that the above option inlines stylesheets, not individual styles on elements.
By default, this option is set to 4kB, meaning small sets of styles will be inlined to avoid additional network requests for a few bytes, while larger stylesheets are turned into external files.
Conditional CSS
In a previous tutorial, we also briefly touched on conditional CSS. As a refresher, we can use the class:list
directive to add CSS conditionally:
The directive accepts an array with a list of values, that can be either a string, a variable, or an object. Internally, this directive uses the popular tiny clsx
utility library. It automatically filters out falsy (false
, null
, or undefined
) values. Given that the type
variable doesn't have a value, the output of the above example will be the following:
<p class="notification show" />
Server-side CSS Variables
In case we need to create dynamic styles based on server-side logic, it's also possible using the define:vars
directive:
---
const color = '#2980b9'
---
<h1>Styled using SSR variable</h1>
<style define:vars={{ color }}>
h1 {
color: var(--color);
}
</style>
The directive must be used on a style
tag. It expects an object where we can pass server-side variables. Using object shorthand notation, we exposed a color
variable to our CSS. This variable is now available as a CSS variable, referenced using the same name (--color
). This approach helps build dynamic CSS using server-side JavaScript code.
CSS Integrations and Preprocessors
Astro also supports third-party CSS libraries and tools. Let's take a look at the two most popular options: Tailwind and Sass.
Apart from Tailwind and Sass, Less, Stylus, and PostCSS are also supported.
Tailwind
To install and start using Tailwind, we only need to run the following command inside the terminal:
npx astro add tailwind
This will do three things for us: automatically install necessary dependencies like Tailwind itself and its Astro integration, create a minimal Tailwind configuration file, and update Astro's configuration file, as Tailwind can be used as an integration.
import { defineConfig } from 'astro/config'
import tailwind from '@astrojs/tailwind'
export default defineConfig({
integrations: [tailwind()]
})
Once added, we can start using it inside components, even with the class:list
directive:
---
const { type } = Astro.props
---
<h1 class:list={[
'text-2xl font-bold',
type === 'secondary' && 'text-blue-600'
]}>
<slot />
</h1>
Sass
Popular preprocessors are also supported through Vite. To use Sass, we only need to install Sass itself:
npm i sass
Once installed, we can start utilizing Sass inside Astro components by setting the lang
attribute on style
tags to either scss
or sass
:
<h1>
<slot />
</h1>
<!-- Uses the SCSS syntax -->
<style lang="scss">
$secondary: #2980b9;
h1 {
color: $secondary;
}
</style>
<!-- Uses the Sass syntax -->
<style lang="sass">
$secondary: #2980b9
h1
color: $secondary
</style>
Passing Classes to Child Components
There's one last thing we need to discuss regarding the use of CSS in Astro. In some cases, we may want to pass classes to a child component; however, there's a catch.
The class
attribute cannot be directly used in child components because class
is a reserved keyword in JavaScript. A quick and easy way to work around this is to spread the props on the element:
<h1 {...Astro.props}>
<slot />
</h1>
However, this approach is quite limited. While it may be okay if we only need a class
prop, it becomes troublesome when using the component with multiple props.
Essentially, this approach spreads every prop on the element, which may not be what we want. To overcome the reserved class
keyword and avoid polluting the element with other props, we can modify the code as follows:
---
const {
class: className,
...rest
} = Astro.props
---
<h1 class={className}>
<slot />
</h1>
When we destructure the props, we can rename the passed class
prop to a different name for use. Now, we can pass a class
prop to the component and use it without any issues.
Summary
In summary, Astro provides various methods for styling components, including scoped, global, inline, conditional, and the use of server-side variables. Additionally, it provides support for popular libraries such as Tailwind and Sass, allowing developers to choose their preferred CSS approach, which can be easily installed and configured with a single command.
Overall, Astro is versatile when it comes to using CSS. If you have any experience with Astro, leave your thoughts in the comments below on how you feel about using CSS with the framework. Thank you for reading through, happy coding!
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: