How to Work With CSS in Astro

How to Work With CSS in Astro

Learn the many ways of styling Astro components
Ferenc Almasi β€’ 2023 November 28 β€’ Read time 9 min read
Learn what are the possible different ways to use and style your components 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:

Copied to clipboard! Playground
<h1>
    <slot />
</h1>

<style>
     h1 {
         color: #2980b9;
     }
</style>
Heading.astro
Using scoped CSS in Astro

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:

Copied to clipboard! Playground
<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 />
Heading.astro
Specificity is preserved

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:

Copied to clipboard!
.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:

Copied to clipboard! Playground
<!-- 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" />
Different ways to use global CSS in Astro
  1. Using the :global pseudo selector.
  2. Using the is:global directive to make all styles within a style tag global.
  3. Using a CSS import inside a layout.
  4. Using a link tag that imports CSS from the public 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:

Copied to clipboard! Playground
<h1>
    <slot />
</h1>

<!-- Scoped CSS: -->
<style>
    h1 {
        color: #2980b9;
    }
</style>

<!-- Global CSS: -->
<style is:global>
    h1 {
        margin-top: 20px;
    }
</style>
Heading.astro
Sharing global styles with scoped CSS

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:

Copied to clipboard! Playground
// 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>
Sharing CSS with layouts

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:

Copied to clipboard!
<h1 style={{ color: '#2980b9' }}>JSX syntax</h1>
<h1 style="color:#2980b9;">HTML syntax</h1>
Inlining CSS in Astro

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:

Copied to clipboard! Playground
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'
    }
})
astro.config.mjs
Inlining styles through Astro's build process

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:

Copied to clipboard! Playground
<p class:list={[
    'notification',
    type,
    { show: true }
]} />
Conditionally applying classes in Astro

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:

Copied to clipboard!
<p class="notification show" />
Output of the class:list directive

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:

Copied to clipboard! Playground
---
const color = '#2980b9'
---

<h1>Styled using SSR variable</h1>

<style define:vars={{ color }}>
    h1 {
        color: var(--color);
    }
</style>
Heading.astro
Passing CSS variables from the server side

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:

Copied to clipboard!
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.

Copied to clipboard! Playground
import { defineConfig } from 'astro/config'
import tailwind from '@astrojs/tailwind'

export default defineConfig({
    integrations: [tailwind()]
})
astro.config.mjs
Astro will add Tailwind as an integration

Once added, we can start using it inside components, even with the class:list directive:

Copied to clipboard! Playground
---
const { type } = Astro.props
---

<h1 class:list={[
    'text-2xl font-bold',
    type === 'secondary' && 'text-blue-600'
]}>
    <slot />
</h1>
Heading.astro
Using Tailwind inside Astro

Sass

Popular preprocessors are also supported through Vite. To use Sass, we only need to install Sass itself:

Copied to clipboard!
npm i sass
Run the command to install Sass

Once installed, we can start utilizing Sass inside Astro components by setting the lang attribute on style tags to either scss or sass:

Copied to clipboard! Playground
<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>
Heading.astro
Using Sass in Astro
Looking to improve your skills? Master Astro + SEO from start to finish.
info Remove ads

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:

Copied to clipboard!
<h1 {...Astro.props}>
    <slot />
</h1>
Heading.astro
Passing class to child components

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:

Copied to clipboard! Playground
---
const {
    class: className,
    ...rest
} = Astro.props
---

<h1 class={className}>
    <slot />
</h1>
Heading.astro
Recommended way to pass class to child components

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!  

  • twitter
  • facebook
Did you find this page helpful?
πŸ“š More Webtips
Mentoring

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:

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.