Astro JS Sass Styling

Astro JS Sass Styling

SCSS Astro Setup

💄 Astro JS Sass Styling

We see how to set up Astro JS Sass styling in this post. Although modern CSS now supports many of the features which developers looked to Sass or SCSS to provide in the past, many developers still appreciate Sass. Astro’s Framework agnosticism as regards to using React together with Svelte, Vue or something else is already well known. You will expect this support of all popular frameworks to extend from rendering to styling. Astro does not disappoint here. In this post we see how you can use Sass to add not just global styles but also scoped styles to your Astro and Svelte components. In fact we can extend that to React by using SCSS modules for styling. To add polish we look at setting up Stylelint to enforce a particular code style or even just to make sure your SCSS stays just how you like it!

🧱 Astro JS Sass Styling: What we’re Building

Rather than build a new app, we have taken the demo app from our Astro JS Tutorial and converted styling to SCSS. In the tutorial, we used vanilla CSS and you will see there is not much to change to get Sass up and running. In this post we just look at the main changes you need to make to your files (rather than provide a follow along tutorial). Let me know if you like this style instead of building an app as you follow along. If you really do want to build something as you read along though, you can clone the Astro JS Tutorial and make changes, converting it to SCSS.

⚙️ Astro JS Sass Styling: Config

We add the Sass package as well as Stylelint here, which we use later.

 pnpm add -D sass stylelint stylelint-config-recommended-scss stylelint-config-standard

🌍 Astro JS Sass Styling: Global Styles

We will create a global styles file at src/styles/styles.scss. As well as changing the syntax to make it more idiomatic Sass, we also import a couple of files:

@charset "UTF-8";

@import './fonts.scss';
@import './variables.scss';

body {
  margin: $spacing-0;
  background: hsl($colour-dark-hue $colour-dark-saturation $colour-dark-luminance);
  font-family: Roboto;
  text-align: center;
}

h1 {
  font-size: 3.815rem;
  margin-bottom: $spacing-12;
}

/* TRUNCATED... */

We place the fonts in a separate file just to keep the global styles file cleaner. However for the variables, beyond style, placing variables in a separate file makes it easier to import them when we want to use the variables in scoped styles within component files. Having a separate variables file lets us import just the variables without needing to import the entire global styles files in these cases. In line 1 we set the charset.

Variables and Fonts

Here is the fonts file for completeness:

/* roboto-regular - latin */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400;
  src: local(''), url('/fonts/roboto-v29-latin-regular.woff2') format('woff2'),
    url('/fonts/roboto-v29-latin-regular.woff') format('woff');
}

/* roboto-700 - latin */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 700;
  src: local(''), url('/fonts/roboto-v29-latin-700.woff2') format('woff2'),
    url('/fonts/roboto-v29-latin-700.woff') format('woff');
}

and also the variables file:

/* colours */
/* honey-yellow */
$colour-brand-hue: 39;
$colour-brand-saturation: 92%;
$colour-brand-luminance: 57%;

/* safety orange blaze orange */
$colour-secondary-hue: 21;
$colour-secondary-saturation: 89%;
$colour-secondary-luminance: 52%;

/* moss green */
$colour-alternative-hue: 84;
$colour-alternative-saturation: 29%;
$colour-alternative-luminance: 43%;

/* pine tree */
$colour-dark-text-hue: 100;
$colour-dark-text-saturation: 18%;
$colour-dark-text-luminance: 13%;

/* old lace */
$colour-light-text-hue: 50;
$colour-light-text-saturation: 66%;
$colour-light-text-luminance: 95%;

$colour-dark-hue: 206;
$colour-dark-saturation: 46%;
$colour-dark-luminance: 37%;

/* spacing */
$spacing-px: 1px;
$spacing-0: 0;
$spacing-4: 1rem;
$spacing-6: 1.5rem;
$spacing-8: 2rem;
$spacing-12: 3rem;
$spacing-32: 8rem;

💅🏽 Scoped Styles in Astro and Svelte Components

Next, let’s look at the home page to see Astro JS Sass style imports together with scoped styling in an Astro file.

---
import '$styles/styles.scss';
import { Markdown } from 'astro/components';
import ReactVideo from '$components/Video.jsx';
import SvelteVideo from '$components/Video.svelte';
---

<html lang="en-GB">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.png" />
    <meta name="viewport" content="width=device-width" />

    <title>RodneyLab Minimal Astro Example</title>
  </head>
  <body>
    <main class="container">
      <h1>Astro JS Tutorial Site</h1>
      <p>This demo is not endorsed by Ben Awad, just thought the video content was fitting!</p>
      <ReactVideo client:load />
      <SvelteVideo client:load />
      <section class="mdx-container">
        <Markdown>
          ## Astro in 100 Seconds

          <div class="video-container">
          <iframe 
          title="Astro in 100 Seconds"
          width="560"
          height="315"
          src="https://www.youtube-nocookie.com/embed/dsTXcSeAZq8"
          frameborder="0"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowfullscreen
          ></iframe>
          </div>
        </Markdown>
      </section>
    </main>
  </body>
</html>

<style lang="scss">
  @import '../styles/variables';

  .container {
    display: flex;
    flex-direction: column;
    background: hsl($colour-dark-hue $colour-dark-saturation $colour-dark-luminance);
    color: hsl($colour-light-text-hue $colour-light-text-saturation $colour-light-text-luminance);
    padding: $spacing-8 $spacing-0 $spacing-32;
  }

  .container a {
    color: hsl($colour-secondary-hue $colour-secondary-saturation $colour-secondary-luminance);
  }
  .mdx-container {
    display: flex;
    flex-direction: column;
    background: hsl(
      $colour-alternative-hue $colour-alternative-saturation $colour-alternative-luminance
    );
    align-items: center;
    width: 100%;
    padding: $spacing-8 $spacing-0 $spacing-12;
    color: hsl($colour-light-text-hue $colour-light-text-saturation $colour-light-text-luminance);
  }
</style>

In line 2 we import the global Sass stylesheet. This is not really that different to how we would import a CSS stylesheet. In fact, the only real difference is the change in filename. Note that we have defined an alias $styles as src/styles in the tsconfig.js file. This lets us use the shorthand which you see in the code. You can see a more detailed explanation of this in the Astro JS tutorial.

We can add scoped styles using a <style> tag (just like with vanilla CSS). We should remember though, when using Sass to add a lang="scss" attribute to the style tag, as in line 43. Because we want to use variables defined in the src/styles/variables.scss within this style element, we can just import that file. We do that in line 44.

Astro JS Sass Styling: Svelte Component

Scoped styles in a Svelte component work just like the Astro file we just looked at. Because this is a component, rather than a page or a layout, we do not import the global style sheet in the Svelte file. Since the component will be embedded in an Astro page, we can rely on the page or its template to ensure global styles are imported.

For completeness, here is the Svelte file. Notice that the container class does not collide with the container we used in the Astro component, because of the scoping.

<script>
  $: altColours = false;
</script>

<section class={`container${altColours ? ' container-alt' : ''}`}>
  <h2>Svelte Component</h2>
  <div class="video-container">
    <iframe
      title="Trying Svelte for the Third Time"
      width="560"
      height="315"
      src="https://www.youtube-nocookie.com/embed/xgER1OutVvU"
      frameborder="0"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
      allowfullscreen
    />
  </div>
  <button
    class={`button${altColours ? ' button-alt' : ''}`}
    on:click={() => {
      altColours = !altColours;
    }}><span class="screen-reader-text">Toggle colours</span></button
  >
</section>

<style lang="scss">
  @import '../styles/variables';

  .container {
    display: flex;
    flex-direction: column;
    background: hsl($colour-brand-hue $colour-brand-saturation $colour-brand-luminance);
    align-items: center;
    width: 100%;
    padding: $spacing-8 $spacing-0;
    color: hsl($colour-dark-text-hue $colour-dark-text-saturation $colour-dark-text-luminance);
  }

  .container-alt {
    background: hsl($colour-secondary-hue $colour-secondary-saturation $colour-secondary-luminance);

    color: hsl($colour-light-text-hue $colour-light-text-saturation $colour-light-text-luminance);
  }

  .button {
    background: hsl($colour-secondary-hue $colour-secondary-saturation $colour-secondary-luminance);
  }

  .button-alt {
    background: hsl($colour-brand-hue $colour-brand-saturation $colour-brand-luminance);
  }
</style>

🧩 React Styled Components

React in Astro does not have an out-of-the-box scoped styling solution as convenient as what we have seen for Svelte and Astro files. Having said that, it is still fairly easy to scope styles using SCSS modules. This involves defining styles in a side file (typically named like the component which uses it) then importing them into the component file. Let's take a closer look.

We have our React Video component defined in src/components/Video.jsx. Then we define its styles in Sass in src/components/Video.module.scss:

@import '../styles/variables.scss';

.container {
  display: flex;
  flex-direction: column;
  background: hsl($colour-secondary-hue $colour-secondary-saturation $colour-secondary-luminance);
  align-items: center;
  width: 100%;
  padding: $spacing-8 $spacing-0;
  color: hsl($colour-light-text-hue $colour-light-text-saturation $colour-light-text-luminance);
}

.container-alt {
  background: hsl(
    $colour-alternative-hue $colour-alternative-saturation $colour-alternative-luminance
  );
}

.button {
  background: hsl(
    $colour-alternative-hue $colour-alternative-saturation $colour-alternative-luminance
  );
}

.button-alt {
  background: hsl($colour-secondary-hue $colour-secondary-saturation $colour-secondary-luminance);
}

Notice, once more, we import the variables for use in the local Sass code. Then, we import all of the styles as a styles object in the React file. The actual scoped class names are defined in the JavaScript fields which we import. Remembering they are encoded in the JavaScript, we use them in the React className attributes:

import styles from '$components/Video.module.scss';
import { useState } from 'react';

export const ReactExample = function ReactExample() {
  const [altColours, setAltColours] = useState(false);

  return (
    <section className={`${styles.container}${altColours ? ` ${styles['container-alt']}` : ''}`}>
      <h2>Example React Component</h2>
      <div className="video-container">
        <iframe
          width="560"
          height="315"
          src="https://www.youtube-nocookie.com/embed/PJ0QSJpJn2U"
          title="Should you Stop Using React"
          frameBorder="0"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowFullScreen
        />
      </div>
      <button
        className={`${styles.button}${altColours ? ` ${styles['button-alt']}` : ''}`}
        onClick={() => {
          setAltColours(!altColours);
        }}
      >
        <span className="screen-reader-text">Toggle colours</span>
      </button>
    </section>
  );
};

export default ReactExample;

In line 1, we import the styles from the module file. Our style module uses the container class. In line 8 we attach the scoped class to the <section> element. We access it via styles.container. You can also add scoped class styles where the styles are attached to a kebab-case class (for example, button-alt). We see this in action where we define styles for the container-alt class in line 13 of src/components/Video.scss. Our code uses the JavaScript object accessor bracket notation, quoted notation for this kebab-case class in our React component. So in this case it is styles['container-alt']. We see the concrete use of this in line 8.

👼🏽 Astro JS Sass Styling: Stylelint

Stylelint is a great tool for enforcing code style in your app styles helpful both when working in a team and on your own personal projects. You can easily build stylelint checks into your continuous integration process.

First create a .stylelintrc.json config file:

{
  "extends": "stylelint-config-recommended-scss",
  "rules": {
    "color-named": "never",
    "font-family-name-quotes": "always-where-required",
    "font-weight-notation": "named-where-possible",
    "function-url-no-scheme-relative": true,
    "function-url-quotes": "always",
    "string-quotes": "single",
    "value-keyword-case": "lower",
    "unit-disallowed-list": [],
    "max-empty-lines": 2,
    "no-descending-specificity": true,
    "no-duplicate-selectors": true,
    "font-family-no-missing-generic-family-keyword": null,
    "property-no-unknown": [
      true,
      {
        "ignoreProperties": ["/^lost-/"]
      }
    ],
    "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"] }]
  },
  "ignoreFiles": ["node_modules/*"],
  "defaultSeverity": "error",
  "customSyntax": "postcss-html"
}

Feel free to customise this to your own tastes. See stylelint docs for details.

Next we will add a lint script to package.json:

{
  "name": "astro-js-sass-styling",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "format": "prettier --write --plugin-search-dir=. .",
    "lint:scss": "stylelint \"src/**/*.{astro,scss,svelte}\"",
    "prettier:check": "prettier --check --plugin-search-dir=. ."
  },

We define the new script in line 11. Here we set stylelint to check .astro, .scss and .svelte files. To run the script, in the Terminal type:

pnpm run lint:scss

astro-js-sass % pnpm lint:scss > astro-js-sass-styling@0.0.1 lint:scss /Users/ra598o6i/Documents/lab/sites/astro/demos/astro-js-sass-styling > stylelint src/**/*.{astro,scss,svelte} src/pages/index.astro 44:11  ✖  Expected single quotes  string-quotes ELIFECYCLE Command failed with exit code 2.  astro-js-sass % astro-js-sass % astro-js-sass % astro-js-sass % pnpm lint:scss > astro-js-sass-styling@0.0.1 lint:scss /Users/ra598o6i/Documents/lab/sites/astro/demos/astro-js-sass-styling > stylelint src/**/*.{astro,scss,svelte}

You can add this into existing Husky configuration. If you want to set this up have a look at the post on 7 Tools to Streamline your CI Workflow.

🙌🏽 Astro JS Sass Styling: Wrapping Up

In this post we have run through the key parts of setting up Astro JS Sass styling. In particular, we looked at:

  • how you can add global styles with Sass in Astro as well as scoped styles in Astro pages and components,
  • using SCSS modules in Astro to add scoped styling to React components,
  • configuration for stylelint in Astro.

The Astro JS Sass styling demo code is in the Rodney Lab GitHub repo. You can also try it on Stackblitz.

I hope you found this article useful and am keen to hear how you will the starter on your own projects as well as possible improvements.

🙏🏽 Astro JS Sass Styling: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also subscribe to the newsletter to keep up-to-date with our latest projects.

Did you find this article valuable?

Support Rodney Lab - Content Site WebDev & Serverless by becoming a sponsor. Any amount is appreciated!