# Styling

Course: [Astro](https://www.devtutorials.ai/learn/astro) · Section: Build Pages
Source: https://www.devtutorials.ai/learn/astro/build-pages/styling

> Scoped styles and how scoping works, styling child components, :global() and is:global, global stylesheets in a layout, CSS order, inline styles, define:vars, styling with props, CSS imports, Tailwind CSS, Sass, PostCSS, CSS Modules, bundling and inlineStylesheets.

## Styling in Astro

- Astro uses plain CSS. You do not need to learn a new styling language.
- You can write CSS inside a component, in a separate file, or with tools like Tailwind CSS.
- By default, a component's CSS only styles that component.
- In this lesson, you will style Asha's site and learn when styles stay local and when they go global.

**Simple lesson:** CSS in an Astro component styles only that component, unless you ask for more.

### A Real-World Example

- Asha's post cards have `h2` titles. Her page also has an `h2` for "Contact".
- She makes card titles purple with `h2 { color: purple; }` inside `PostCard.astro`.
- In plain HTML, that rule would turn **every** `h2` on the page purple.
- In Astro, only the card titles turn purple. The "Contact" heading stays black.

Press **▶ Run**. Only the card heading is purple.

    <h2>{title}</h2>
    <p>A short summary of the post.</p>
</article>

<style>
    article {
      border: 1px solid #ddd6fe;
      border-radius: 8px;
      padding: 8px 12px;
      margin-bottom: 10px;
    }
    h2 {
      color: rebeccapurple;
      margin: 0;
    }
</style>
`,
    "src/pages/index.astro": `---
---

<h2>Contact</h2>
<p>Write to asha@example.com</p>
`,
  }}
/>

What happened:

- The `<style>` tag is inside `PostCard.astro`.
- Its `h2` rule reached the card's heading, but not the page's "Contact" heading.
- Open **HTML sent to browser**. The card's tags carry an extra attribute, such as `data-astro-cid-...`.

### What You Will Learn

In this lesson, you will learn:

1. How scoped styles work, and why they are safe.
2. How to style a child component or slotted content.
3. How to add global styles, and where.
4. How CSS order decides which rule wins.
5. How to use values from the script in CSS.
6. How to import CSS files and npm CSS.
7. How Tailwind CSS, Sass, PostCSS and CSS Modules fit in.
8. How Astro bundles your CSS for the browser.

## Scoped Styles

### style Tags in Components

- Write a `<style>` tag anywhere in a component's template. Most people put it at the end.
- Write normal CSS inside it.
- Astro removes the tag from the HTML and puts the CSS in the page's stylesheet.

### How Scoping Works

- **Scoped style** = a rule that applies only to the component it is written in.
- Astro adds a unique attribute, such as `data-astro-cid-abc123`, to every tag in the component.
- It also adds that attribute to every selector in the component's CSS.
- So `h2 { ... }` becomes `h2[data-astro-cid-abc123] { ... }`. It can only match that component's `h2`.

<figure className="ast-fig ngc-fig">
<svg viewBox="0 0 640 210" role="img" aria-labelledby="fig-scope" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-scope">Animation: the rule h2 color purple gets a unique attribute added. It then matches only the h2 inside the card, not the h2 outside.</title>
  <defs><marker id="sc-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 z" style={{ fill: "var(--ngc-slate)" }} /></marker></defs>
  <rect x="20" y="30" width="250" height="44" rx="8" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)" }} />
  <text x="145" y="57" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700 }}>h2 {"{"} color: purple; {"}"}</text>
  <path d="M145 74 V104" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#sc-arrow)" />
  <rect x="20" y="108" width="250" height="44" rx="8" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)" }} />
  <text x="145" y="135" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--mono)", fontSize: 12, fontWeight: 700 }}>h2[data-astro-cid-x1] {"{"} ... {"}"}</text>
  <text x="145" y="176" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 12 }}>Astro adds the component's ID</text>
  <rect x="340" y="24" width="280" height="80" rx="10" style={{ fill: "var(--ngc-win)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }} />
  <text x="352" y="44" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 11 }}>PostCard (has data-astro-cid-x1)</text>
  <text x="480" y="80" textAnchor="middle" style={{ fontFamily: "var(--sans)", fontSize: 18, fontWeight: 700, fill: "var(--ink)" }}><animate attributeName="fill" values="#1f2328;#1f2328;#663399;#663399" keyTimes="0;0.4;0.5;1" dur="5s" repeatCount="indefinite" />Card title</text>
  <rect x="340" y="124" width="280" height="64" rx="10" style={{ fill: "var(--ngc-win)", stroke: "var(--ngc-slate)", strokeWidth: 1.5 }} />
  <text x="352" y="144" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 11 }}>Page (no x1 attribute)</text>
  <text x="480" y="172" textAnchor="middle" style={{ fill: "var(--ink)", fontFamily: "var(--sans)", fontSize: 18, fontWeight: 700 }}>Contact</text>
</svg>
<figcaption>The rule gets the component's ID, so it only matches tags with that ID.</figcaption>
</figure>

Scoped styles let you use short, simple selectors like `h2` and `p`. They can never break another component.

### Styling Child Components

- Scoped styles stop at a component's edge.
- A parent's `p { }` rule does **not** reach the `<p>` inside a child component.
- Styles for a child belong in the child.
- If the parent must decide a child's look, pass a prop or a `class` (see below).

### Using :global()

- `:global()` turns one selector back into a normal, unscoped selector.
- Common use: style the HTML that arrives through a `<slot />`, or HTML from `set:html`.
- Keep the scoped part in front, so the rule stays inside the component: `.prose :global(a) { ... }`.

Press **▶ Run**. The link comes through the slot. Only `:global()` can style it.

    <slot />
</div>

<style>
    .prose {
      max-width: 40ch;
    }
    /* The link is written in the page, so it has the page's ID, not this one. */
    .prose :global(a) {
      color: darkorange;
      font-weight: bold;
    }
</style>
`,
    "src/pages/index.astro": `---
---

    <p>Read my <a href="/blog">latest post</a> about scoped CSS.</p>
<p>This <a href="/about">link</a> is outside, so it stays plain.</p>
`,
  }}
/>

- Only the link inside `.prose` is orange.
- Without `:global()`, the rule would match nothing, because the link carries the page's ID.

**Simple lesson:** styles are scoped by default. Use `:global()` for one selector that must reach in.

## Global Styles

### is:global

- `<style is:global>` turns off scoping for the whole tag.
- Every rule in it styles the whole page, like a normal stylesheet.
- Use it rarely. Prefer a global CSS file.

A component with `is:global` styles changes every page it appears on. A rule like `h1 { color: red }` there can surprise you far away from the component.

### Importing a Global Stylesheet

- Put site-wide CSS in a file, such as `src/styles/global.css`.
- Import it in a component's script: `import "../styles/global.css";`.
- Astro adds it to every page that uses that component.

### Global Styles in a Layout

- The best place for the import is your base layout.
- Every page uses the layout, so every page gets the global CSS.
- Put fonts, colours, body spacing and basic link styles there.

### CSS Order and the Cascade

- **Cascade** = the browser's rules for picking a winner when two CSS rules clash.
- A more specific selector wins. `h2[data-astro-cid-x1]` is more specific than `h2`.
- So a component's scoped rule usually beats a global rule for the same tag.
- When two rules are equally specific, the one loaded later wins.

Press **▶ Run**. The global file sets the body. The component's scoped `h1` rule beats the global `h1` rule.

    <head>
      <meta charset="utf-8" />
      <title>{title}</title>
    </head>
    <body>
      <slot />
    </body>
</html>
`,
    "src/pages/index.astro": `---
---

    <h1>Global h1: teal</h1>
    <p>Every page gets the global body styles. <a href="/">A link</a></p>
`,
    "src/components/Banner.astro": `<h1>Scoped h1: crimson</h1>

<style>
    h1 {
      color: crimson;
    }
</style>
`,
  }}
/>

**Simple lesson:** put global CSS in a file and import it in the base layout. Scoped rules usually win over global ones.

## Dynamic Styles

### Inline Styles

- You can still write `style="..."` on a tag.
- Astro also accepts an object: `style={{ color: accent, fontSize: "1.2rem" }}`.
- Use inline styles for one-off values. Use classes for anything repeated.

<div style="background: #e5e7eb; border-radius: 6px; width: 240px;">
    <div style={{ width: progress + "%", background: accent, color: "white", padding: "4px 8px", borderRadius: "6px" }}>
      {progress}%
    </div>
</div>
`,
  }}
/>

- The bar's width comes from `progress`. Change it to `90` and run again.
- In the object form, CSS names are written in camelCase: `borderRadius`, not `border-radius`.

### define:vars in style

- `define:vars` passes variables from the script into a `<style>` tag.
- Write `<style define:vars={{ accent }}>`. Inside, use `var(--accent)`.
- Astro turns each variable into a **CSS custom property** on the component.

### Styling with Props

- Combine props and `define:vars` to let the parent choose a colour.
- Or combine props and `class:list` to switch between ready-made classes.

<style define:vars={{ color }}>
    .badge {
      background: var(--color);
      color: white;
      border-radius: 999px;
      padding: 2px 10px;
      margin-right: 6px;
    }
    .large {
      font-size: 1.3rem;
    }
</style>
`,
    "src/pages/index.astro": `---
---

<h1>Topics</h1>
`,
  }}
/>

- Each badge gets its own `--color` value.
- `size` picks the `small` or `large` class.

**Simple lesson:** `define:vars` sends script values into CSS. Props let the parent choose them.

## CSS Imports and Tools

### Importing CSS Files

- `import "../styles/global.css";` in a script adds the file to the page.
- Astro bundles it with the rest of your CSS.

### Importing CSS from npm Packages

- Some packages ship CSS. Import it by its package path:

```astro
---
import "open-props/style";
---
```

### Linking Stylesheets from public/

- A file in `public/` can be linked with a normal tag: `<link rel="stylesheet" href="/theme.css" />`.
- Astro does **not** process or shrink it. Use this only for files that must stay as they are.

### Adding Tailwind CSS

- **Tailwind CSS** = a tool where you style with small classes, such as `text-xl` and `p-4`.
- Add it with one command:

```bash
npx astro add tailwind
```

- It installs Tailwind, adds its Vite plugin to the config and creates `src/styles/global.css` with `@import "tailwindcss";`.
- Import that file in your layout. Then use classes: `<h1 class="text-3xl font-bold">`.

### Sass, Less and Stylus

- **Sass** = CSS with extra features, such as variables and nesting.
- Install it: `npm install sass`. Then write `<style lang="scss">`.
- Less and Stylus work the same way: install the package, then use `lang="less"` or `lang="styl"`.

### PostCSS

- **PostCSS** = a tool that changes CSS with plugins, such as adding browser prefixes.
- Astro runs it already. Add a `postcss.config.cjs` file to add plugins.

### CSS Modules

- **CSS Module** = a `.module.css` file whose class names are made unique.
- Import it as an object: `import styles from "./Card.module.css";`.
- Use it: `<div class={styles.card}>`.
- Most Astro projects do not need them, because `<style>` is already scoped.

In this lab, `.css` imports and `<style>` tags work. Tailwind, Sass and CSS Modules need npm packages, so try them on your own computer.

**Simple lesson:** plain CSS covers most needs. Add Tailwind or Sass with one install when you want them.

## Style Output

### Bundling and Minification

- **Bundling** = joining many CSS pieces into a few files.
- **Minification** = removing spaces and comments so files are smaller.
- `astro build` does both. Each page gets only the CSS of the components it uses.

### Inlining Stylesheets

- **Inline stylesheet** = CSS placed inside a `<style>` tag in the HTML, instead of a separate `.css` file.
- Small CSS loads faster inline, because the browser makes no extra request.
- `build.inlineStylesheets` in the config decides:
  - `"auto"` (the default) = inline small stylesheets, link big ones.
  - `"always"` = inline all of them.
  - `"never"` = always use separate `.css` files.

**Simple lesson:** Astro bundles, shrinks and sends each page only the CSS it needs.

## TL;DR

1. **Scoped style** = `<style>` in a component. Only styles that component.
2. **How** = Astro adds a `data-astro-cid-...` attribute to tags and selectors.
3. **:global(sel)** = one unscoped selector. Use it for slotted or `set:html` content.
4. **is:global** = the whole `<style>` tag is unscoped.
5. **Global CSS** = a file imported in the base layout.
6. **Cascade** = more specific wins. Scoped rules usually beat global ones.
7. **define:vars** = script values as CSS variables.
8. **Tools:**
   - Tailwind = `npx astro add tailwind`,
   - Sass = `npm install sass` + `lang="scss"`,
   - CSS Modules = `*.module.css`.
9. **inlineStylesheets** = `auto`, `always` or `never`.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Styling a child's tags from the parent | Parent CSS reaches everything | Style inside the child, or use a prop | Nothing changes |
| Styling slotted HTML without `:global()` | Slot content has the component's ID | Use `.wrapper :global(a)` | The rule matches nothing |
| Using `is:global` everywhere | Global is simpler | Use a global CSS file, and keep components scoped | Styles clash across the site |
| Global CSS imported on one page only | Imports affect every page | Import it in the base layout | Other pages look unstyled |
| `color: accent` in CSS with `define:vars` | The variable name works as is | Write `var(--accent)` | The colour is ignored |
| `<link href="/src/styles/global.css">` | `src` files are served | Import `src` CSS in a script | The file is not found |

</div>

## Check Yourself

## Exercises

## What to Remember

- A `<style>` in a component is scoped to that component.
- Use `:global()` for slotted or `set:html` content.
- Import global CSS in the base layout.
- `define:vars` and props let the parent choose styles.
- Tailwind and Sass are one install away. Astro bundles and shrinks all CSS.

Next: **Client-Side Scripts**, where Asha adds the first interactive features.
