# How Astro Handles CSS Processing: Scoped Styles and Vite Integration

> Discover how Astro processes CSS with Vite integration for scoped styles. Learn about style extraction, Vite's pipeline, and global CSS compatibility.

- Repository: [Astro/astro](https://github.com/withastro/astro)
- Tags: internals
- Published: 2026-03-06

---

**Astro processes CSS by extracting `<style>` blocks from `.astro` components, passing them through Vite's transformation pipeline, and applying unique hash-based scoping to prevent style leakage while maintaining full compatibility with preprocessors, CSS Modules, and global styles.**

Astro treats CSS as a first-class asset within the `withastro/astro` repository, providing a built-in scoped-style system that integrates seamlessly with Vite's powerful CSS pipeline. This architecture allows developers to write component-level styles that are automatically encapsulated without sacrificing access to modern CSS tooling like PostCSS, Sass, or Tailwind.

## How Astro Processes CSS: The Core Pipeline

Astro's CSS processing follows a distinct five-stage workflow that bridges its compiler with Vite's bundler:

1. **Parsing** – The compiler analyzes `.astro` files using [`packages/astro/src/compiler/parse.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/compiler/parse.ts) to identify and extract `<style>` blocks.
2. **Vite Transformation** – Extracted CSS is handed to Vite's loaders via [`packages/astro/src/integrations/vite-plugin-astro.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/integrations/vite-plugin-astro.ts), enabling preprocessing through PostCSS, Sass, Less, or Tailwind.
3. **Hash Generation** – The [`packages/astro/src/compiler/css.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/compiler/css.ts) module generates a unique hash (e.g., `astro-1a2b3c`) for the component.
4. **Selector Rewriting** – Astro rewrites CSS selectors to include the hash attribute (e.g., `[data-astro-1a2b3c] selector`), while leaving `:global()` rules unchanged.
5. **Bundling** – Vite emits the final CSS bundle, either as a single file or code-split chunks for lazy-loaded components.

This pipeline ensures that scoped styles receive full preprocessor support while maintaining strict encapsulation.

## Scoped Styles in Astro Components

Adding a `<style>` tag inside an `.astro` component automatically scopes those rules to that component only. During compilation, Astro generates a unique hash and rewrites selectors to target elements marked with a corresponding `data-astro-hash` attribute.

Consider this basic example:

```astro
---
// src/pages/index.astro
---
<html>
  <head></head>
  <body>
    <h1>Hello Astro</h1>

    <style>
      h1 { color: teal; }
    </style>
  </body>
</html>

```

The compiler transforms this into HTML where the `h1` receives a data attribute and the selector is rewritten:

```html
<h1 data-astro-1a2b3c>Hello Astro</h1>
<style data-astro-css>
  [data-astro-1a2b3c] h1{color:teal}
</style>

```

The component's root element receives the `data-astro-hash` attribute at runtime, ensuring styles do not leak to child or parent components.

## Using Vite CSS Preprocessors with Scoped Styles

Astro supports Vite plugins for css preprocessing on scoped styles. When you specify a language attribute like `lang="scss"`, the extracted style text is fed through Vite's CSS loaders before the hash is applied.

```astro
---
// src/components/Button.astro
---
<button class="primary">Click</button>

<style lang="scss">
  $brand: #ff4500;
  .primary {
    background: $brand;
    &:hover { background: darken($brand, 10%); }
  }
</style>

```

According to the `withastro/astro` source code, this means you get all the usual Vite transforms while keeping the hash-based scoping intact.

## CSS Modules Integration

Astro supports CSS Modules via the `module` attribute (`<style module>`), implemented in [`packages/astro/src/integrations/css-modules.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/integrations/css-modules.ts). Vite's CSS-Modules plugin processes the file, returning an object of class-name mappings that Astro injects as a `styles` prop.

```astro
---
// src/components/Card.astro
---
<div class={styles.card}>
  <slot />
</div>

<style module>
  .card {
    padding: 1rem;
    border: 1px solid #ddd;
  }
</style>

```

The `styles.card` reference resolves to a generated class name like `card_xyz123`, providing local scope through Vite's CSS Modules implementation rather than Astro's hash-based scoping.

## Global Styles and the `:global` Selector

You can opt-out of scoping using the `:global` pseudo-selector or by importing global CSS files. The `:global` selector is left unchanged during the hash rewrite, allowing those rules to apply site-wide.

```astro
---
// src/layouts/BaseLayout.astro
---
<head>
  <link rel="stylesheet" href="/global.css" />
</head>

<style>
  :global(body) {
    margin: 0;
    font-family: system-ui, sans-serif;
  }
</style>

<slot />

```

This approach ensures that specific rules within a component's style block apply globally while other rules remain scoped.

## SSR and Lazy Loading Optimizations

During server-side rendering, Astro prevents flash of unstyled content (FOUC) by inlining scoped CSS directly into the HTML document's `<head>`. The server implementation in [`packages/astro/src/runtime/server/render/template.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/server/render/template.ts) builds a list of collected scoped styles per request and concatenates them into the rendered markup.

For lazy-loaded components, Astro treats scoped CSS as part of the component's JavaScript chunk. When a component is rendered dynamically via `import()`, Vite code-splits the CSS alongside the component, and [`packages/astro/src/runtime/client/index.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/index.ts) ensures the styles are applied client-side when the chunk loads.

```astro
---
// src/pages/blog.astro
const Comments = lazy(() => import('../components/Comments.astro'));
---
<article>…</article>
<Comments client:load />

```

This integration allows scoped styles to load on-demand without blocking the initial render.

## Summary

- **Scoped styles** are automatic in `.astro` components, using hash-based selector rewriting via [`packages/astro/src/compiler/css.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/compiler/css.ts).
- **Vite integration** passes all extracted CSS through Vite's loaders, supporting Sass, Less, Tailwind, and PostCSS through [`packages/astro/src/integrations/vite-plugin-astro.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/integrations/vite-plugin-astro.ts).
- **CSS Modules** are available via `<style module>`, processed by Vite's CSS-Modules plugin and mapped through [`packages/astro/src/integrations/css-modules.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/integrations/css-modules.ts).
- **SSR optimization** inlines critical CSS to prevent FOUC, while **lazy loading** code-splits styles with their components.
- **Global escape hatches** like `:global()` allow specific rules to bypass scoping when needed.

## Frequently Asked Questions

### How does Astro scope styles to specific components?

Astro extracts `<style>` blocks during compilation in [`packages/astro/src/compiler/parse.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/compiler/parse.ts), generates a unique hash in [`packages/astro/src/compiler/css.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/compiler/css.ts), and rewrites selectors to target `[data-astro-hash]` attributes. The component's root element receives this data attribute at runtime, ensuring styles only apply to that component's DOM tree.

### Can I use Sass or Tailwind with Astro's scoped styles?

Yes. Astro feeds extracted style text through Vite's CSS loaders before applying the scope hash. This means you can use `lang="scss"`, Tailwind directives, or PostCSS plugins in `<style>` blocks while maintaining component encapsulation. The transformation happens in [`packages/astro/src/integrations/vite-plugin-astro.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/integrations/vite-plugin-astro.ts).

### What is the difference between `<style>` and `<style module>` in Astro?

Standard `<style>` tags use Astro's built-in hash-based scoping, rewriting selectors to include a data attribute. `<style module>` activates Vite's CSS Modules system via [`packages/astro/src/integrations/css-modules.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/integrations/css-modules.ts), generating unique class names and returning them as a `styles` object for explicit class binding.

### How does Astro prevent flash of unstyled content (FOUC) during SSR?

During server-side rendering, [`packages/astro/src/runtime/server/render/template.ts`](https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/server/render/template.ts) collects all scoped styles used in the request, concatenates them, and injects them directly into the HTML `<head>`. This ensures styles are present before the content renders, eliminating FOUC.