Skip to content
Cloud AI エンジニア入門ガイド
Go back

04. Astroのレイアウトとスタイリング

この章の全体像として、レイアウト、入れ子にする、スコープ付きCSS、全体への適用の4つを番号順に並べ、崩れないCSSの当て方を選べるようになることを示した図

シリーズ目次前章: 03. .astroコンポーネントの基本次章: 05. Astroのファイルベースルーティング

Table of contents

Open Table of contents

この章のねらい

ヘッダー、フッター、<head> のメタ情報。全ページで同じものを毎回書くのは無駄ですし、直し漏れが必ず出ます。

そこで使うのがレイアウトです。あわせて、見た目を整えるスタイリングの基本も見ていきます。

レイアウトコンポーネントとは

レイアウトの正体は、ページ全体の外枠を担当するただの .astro コンポーネントです。専用の仕組みが用意されているわけではありません。慣例として src/layouts/ に置きます。

---
// src/layouts/BaseLayout.astro
interface Props {
  title: string;
  description?: string;
}

const { title, description = "サイトの説明" } = Astro.props;
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
    <meta name="description" content={description} />
  </head>
  <body>
    <header>
      <nav>
        <a href="/">ホーム</a>
        <a href="/about/">このサイトについて</a>
      </nav>
    </header>

    <main>
      <slot />
    </main>

    <footer>
      <p>© 2026</p>
    </footer>
  </body>
</html>

<html> から </html> までをレイアウトが丸ごと持ちます。個々のページは <slot /> に入る中身だけを書けば済みます。

使う側のページはこうなります。

---
// src/pages/about.astro
import BaseLayout from "../layouts/BaseLayout.astro";
---

<BaseLayout title="このサイトについて">
  <h1>このサイトについて</h1>
  <p>Astroの学習記録を公開しています。</p>
</BaseLayout>

レイアウトを入れ子にする

ページの種類が増えると、「全ページ共通の部分」と「記事ページだけ共通の部分」を分けたくなります。そんなときは、レイアウトを入れ子にします。

記事ページがBlogPostLayoutを使い、そのBlogPostLayoutがさらにBaseLayoutを内側で使う入れ子構造を示した図

---
// src/layouts/BlogPostLayout.astro
import BaseLayout from "./BaseLayout.astro";

interface Props {
  title: string;
  pubDate: Date;
}

const { title, pubDate } = Astro.props;
---

<BaseLayout title={title}>
  <article>
    <h1>{title}</h1>
    <time datetime={pubDate.toISOString()}>
      {pubDate.toLocaleDateString("ja-JP")}
    </time>
    <slot />
  </article>
</BaseLayout>

BlogPostLayout は、自分の <slot /> で受け取った内容を <article> で包み、それをさらに BaseLayout に渡しています。こうすることで、共通部分の重複をなくしながら、ページ種別ごとの装飾を追加できます。

Markdownからレイアウトを指定する

Markdownファイルをページとして使う場合、フロントマターの layout プロパティにレイアウトのパスを書くだけで適用できます。

---
layout: ../../layouts/BlogPostLayout.astro
title: "はじめての記事"
pubDate: 2026-08-02
---

ここが本文です。Markdownで書けます。

レイアウト側では、Markdownのフロントマターを Astro.props.frontmatter から取得できます。

---
// src/layouts/BlogPostLayout.astro
const { frontmatter } = Astro.props;
---

<h1>{frontmatter.title}</h1>
<slot />

ただし、記事をまとまった単位で管理するなら、フロントマターの型チェックが効くコンテンツコレクションを使うほうが安全です。詳しくは06. Astro コンテンツコレクションで記事を管理するで扱います。

スタイリングの基本:スコープ付きCSS

Astroの <style> タグに書いたCSSは、既定でそのコンポーネント内だけに適用されます。これをスコープ付きCSS(scoped CSS)と呼びます。

<h1>タイトル</h1>

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

この h1 { color: crimson; } は、同じファイルの <h1> にしか効きません。他のコンポーネントの <h1> は影響を受けません。

CSSは書けば書くほど、どこに影響するのか分からなくなります。Astroは既定でこの問題を封じました。セレクタの命名規則を工夫しなくても衝突しない。地味ですが、これがいちばん効きます。

内部的には、Astroが要素に一意の属性を付け、セレクタにその属性条件を追加することで実現しています。

グローバルに適用したいとき

リセットCSSやサイト全体の基本スタイルなど、あえて全体に効かせたいものもあります。その場合は is:global を付けます。

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

または、外部のCSSファイルをインポートする方法もあります。インポートしたCSSはグローバルに適用されます。

---
import "../styles/global.css";
---

サイト全体に効かせたいスタイルは、レイアウトコンポーネントで一度インポートしておくのが定番です。

スコープ付きとグローバルで、効く範囲は次のように変わります。

styleタグに書いたCSSは書いたコンポーネントだけに効き、is:globalを付けるか外部CSSをインポートするとページ全体のコンポーネントに効くことを、同じページの模式図で塗り分けて比較した図

JavaScriptの値をCSSに渡す:define:vars

コンポーネントスクリプトで計算した値をCSSで使いたいときは、define:vars ディレクティブを使います。指定した値がCSSカスタムプロパティ(CSS変数)として利用可能になります。

---
const accentColor = "rgb(24 121 78)";
const columns = 3;
---

<div class="grid">
  <slot />
</div>

<style define:vars={{ accentColor, columns }}>
  .grid {
    display: grid;
    grid-template-columns: repeat(var(--columns), 1fr);
    border-top: 4px solid var(--accentColor);
  }
</style>

Propsで受け取った値をそのまま見た目に反映できるため、「色違いのカード」「幅の違うレイアウト」といったバリエーションを素直に表現できます。

Sassなどのプリプロセッサ

lang 属性を付けると、SassやSCSS、Lessなども使えます(対応するパッケージのインストールは必要です)。

<style lang="scss">
  $primary: crimson;

  .title {
    color: $primary;

    &:hover {
      opacity: 0.8;
    }
  }
</style>

Tailwind CSSを使う

ユーティリティクラスでスタイルを当てるTailwind CSSも利用できます。Astro 5.2以降はTailwind 4に対応しており、次のコマンドで導入できます。

npx astro add tailwind

導入後は、グローバルCSSでTailwindを読み込みます。

/* src/styles/global.css */
@import "tailwindcss";

あとはレイアウトでこのCSSをインポートすれば、各コンポーネントでユーティリティクラスを使えます。

<h1 class="text-3xl font-bold text-slate-800">タイトル</h1>

スコープ付きCSSとTailwindは併用できます。基本はTailwindで書き、込み入った部分だけ <style> に逃がす。自分はこの分け方に落ち着きました。

クラス名を条件で組み立てる:class:list

条件によってクラスを付け外ししたいときは、class:list ディレクティブが便利です。配列やオブジェクトを渡すと、Astroが適切な文字列に組み立ててくれます。

---
interface Props {
  isActive?: boolean;
  size?: "sm" | "lg";
}

const { isActive = false, size = "sm" } = Astro.props;
---

<button class:list={["btn", `btn-${size}`, { "is-active": isActive }]}>
  <slot />
</button>

オブジェクトで渡した項目は、値が真のときだけクラスとして出力されます。文字列連結や三項演算子を重ねるより読みやすくなります。

同じ記述でも、条件の値によって出力されるクラス名が変わります。

class:listに配列とオブジェクトを渡したとき、オブジェクトの値が偽なら該当クラスは出力されず、真なら追加されることを、isActiveがfalseの場合とtrueの場合の2ケースで比較した図

スタイルの優先順位

複数の指定が競合したときの強さは、公式ドキュメントによると次の順です。

  1. <head> 内の <link> タグで読み込んだCSS(最も弱い)
  2. インポートしたCSS
  3. スコープ付きCSS(最も強い)

スタイルが競合したときの強さが、linkタグで読み込んだCSSが最も弱く、インポートしたCSSが中間、スコープ付きCSSが最も強いという三段の順序になっていることを示した図

コンポーネント固有の指定が共通スタイルに負けないようになっています。ただし同じ強さで競合したら、あとは通常のCSSの詳細度ルール次第です。ここが複雑になってきたら、設計を見直す合図です。

要点

参考資料


シリーズ目次前章: 03. .astroコンポーネントの基本次章: 05. Astroのファイルベースルーティング


Share this post:

Previous Post
05. Astroのファイルベースルーティング
Next Post
03. .astroコンポーネントの基本