Styling in Astro
Astro gives you first-class CSS without forcing a runtime, a framework, or a build plugin you did not ask for. You write plain <style> tags inside .astro components, and Astro scopes them automatically so styles never leak between components. When you need wider reach there are global styles, CSS Modules, and full preprocessor support — all bundled and optimized at build time so the browser only downloads the CSS each page actually uses. This page surveys every option and when to reach for each.
Scoped styles by default
Any <style> tag placed inside a .astro component is scoped to that component. Astro accomplishes this by appending a unique data attribute (such as data-astro-cid-xxxxxx) to the component’s elements and rewriting your selectors to match it. The result is that a .title rule in one component cannot accidentally style a .title in another.
---
// src/components/Card.astro
const { heading } = Astro.props;
---
<article class="card">
<h2 class="title">{heading}</h2>
<slot />
</article>
<style>
.card {
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 1rem;
}
.title {
font-size: 1.25rem;
margin: 0 0 0.5rem;
}
</style>
Astro compiles the .card selector to something like .card[data-astro-cid-abc123], so these rules apply only to this component’s markup. You write ordinary, framework-free CSS and get encapsulation for free.
Scoped styles still cascade from global rules and inherited properties (like
colororfont-family). Scoping prevents selector collisions; it does not create a hermetic shadow boundary.
Global styles
Sometimes you genuinely want a rule to apply everywhere — resets, typography defaults, or design tokens. You have two ways to opt out of scoping.
Use the is:global directive on a <style> tag, or wrap specific selectors in :global():
<style is:global>
:root {
--brand: #6d28d9;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
}
</style>
<style>
/* scoped wrapper, but the inner selector escapes scoping */
:global(.prose a) {
color: var(--brand);
}
</style>
For project-wide stylesheets, create a plain .css file and import it from your layout or component script. Imported CSS is global, bundled, and deduplicated automatically.
---
// src/layouts/Base.astro
import "../styles/global.css";
---
CSS Modules
When you want hashed, collision-proof class names you can reference from JavaScript or framework components, use CSS Modules. Any file ending in .module.css is treated as a module: each class is exported as a property whose value is a uniquely generated name.
// src/styles/button.module.css
.primary {
background: var(--brand);
color: white;
padding: 0.5rem 1rem;
}
---
import styles from "../styles/button.module.css";
---
<button class={styles.primary}>Save</button>
CSS Modules shine inside React, Vue, Svelte, or Solid islands where you import the class map directly into the component. In plain .astro files, scoped <style> tags usually do the job with less ceremony.
Preprocessors and CSS tooling
Astro supports Sass, SCSS, Less, and Stylus out of the box — install the compiler and change the file extension or the lang attribute. No config is required.
npm install sass
<style lang="scss">
$radius: 0.5rem;
.panel {
border-radius: $radius;
&:hover {
box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
}
}
</style>
External preprocessor files import just like CSS:
---
import "../styles/theme.scss";
---
Utility-first frameworks integrate through the official integrations. Tailwind, for example, installs via the CLI and wires up automatically:
npx astro add tailwind
PostCSS works transparently too: add a postcss.config.cjs at the project root and Astro applies it to all CSS, including the contents of <style> tags.
Choosing an approach
| Approach | Scope | Best for |
|---|---|---|
<style> tag | Component-scoped | Most component CSS |
<style is:global> | Global | Resets, tokens, base typography |
Imported .css | Global | Shared stylesheets, third-party CSS |
*.module.css | Hashed/local | Framework islands, JS-referenced classes |
Preprocessor (lang) | Same as host tag | Nesting, variables, mixins |
Bundling and load order
Astro collects all CSS — scoped, global, imported, and preprocessed — and bundles it during the build. It splits and links stylesheets per route so each page downloads only the CSS it needs, and inlines small stylesheets to cut requests. Within a single component, imported stylesheets are applied before that component’s own <style> tag, which keeps your scoped overrides winning the cascade.
Best practices
- Default to scoped
<style>tags; reach for global styles only for resets, tokens, and shared typography. - Keep design tokens (colors, spacing, fonts) as CSS custom properties on
:rootin one global stylesheet. - Use
:global()surgically inside a scoped block rather than making the whole tag global. - Prefer CSS Modules when a framework island needs to reference class names from JavaScript.
- Install a preprocessor only if you need it; modern CSS nesting and custom properties cover many cases without one.
- Let Astro handle bundling — avoid manually linking stylesheets in
<head>when an import will do. - Co-locate component styles with the component so encapsulation and ownership stay obvious.