サイトアイコン IT & ライフハックブログ|学びと実践のためのアイデア集

Next.js 14 App Router×Tailwind CSSで学ぶレスポンシブUI実装

coding script

Photo by Markus Spiske on Pexels.com

Next.js 14のApp RouterとTailwind CSSを組み合わせると、画面の構造と見た目の調整を分けながら、モバイルからデスクトップまで対応するUIを組み立てられます。

この記事では、プロジェクトの作成からレイアウト、画像、アクセシビリティの確認までを、一つの流れとして説明します。

レスポンシブUIとは、画面幅が変わっても情報の読みやすさと操作のしやすさを保てるように、配置や余白、表示方法を調整するUIです。

なお、初期設定の例はNext.js 14とTailwind CSS v3系の構成を前提にしています。

別のバージョンを使う場合は、インストール方法と設定ファイルの形式を各バージョンの公式手順に合わせてください。

App RouterとTailwind CSSの役割

App Routerは、appディレクトリ内のフォルダとpage.tsx、layout.tsxを使って、URLと画面構造を組み立てる仕組みです。

Tailwind CSSは、余白、幅、配置、色などを表すユーティリティクラスをJSXに指定して見た目を整えるCSSフレームワークです。

ReactとNext.jsの役割を先に整理したい場合は、ReactとNext.jsの違いを解説した記事も参考になります。

Tailwind CSSのブレークポイントはモバイルファーストです。

接頭辞のないクラスを小さい画面の基本形として指定し、md:やlg:を付けたクラスで、それ以上の幅における表示を上書きします。

記述 役割 例
接頭辞なし すべての画面幅の基本 grid-cols-1
md: 中幅以上で上書き md:grid-cols-2
lg: 大幅以上で上書き lg:grid-cols-3

プロジェクトを作成する

Next.js 14向けのプロジェクトを新しく作る場合は、バージョンを合わせたcreate-next-appを実行します。

npx create-next-app@14 my-app
cd my-app
npm run dev

対話形式の質問では、TypeScript、Tailwind CSS、App Routerを使用する設定を選びます。

この手順ではTailwind CSSのパッケージと設定が作成されるため、同じパッケージを改めて追加する必要はありません。

生成されたapp/globals.cssがapp/layout.tsxから読み込まれていることも確認します。

ルートレイアウトはすべてのページを包むため、文書の言語や共通ヘッダーなど、サイト全体に共通する要素を置く場所です。

import "./globals.css"

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  )
}

画面の構造をディレクトリに対応させる

App Routerでは、フォルダがURLの区切りに対応し、page.tsxがそのURLで表示する画面になります。

layout.tsxは同じフォルダ以下のページを包むため、ヘッダーやサイドバーなどの共通UIを重複させずに配置できます。

my-app/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   └── dashboard/
│       ├── layout.tsx
│       ├── page.tsx
│       └── settings/
│           └── page.tsx
├── components/
│   ├── Header.tsx
│   ├── Sidebar.tsx
│   └── Card.tsx
└── package.json

この構成では、app/layout.tsxがサイト全体を包み、その内側でapp/dashboard/layout.tsxがダッシュボード配下だけを包みます。

レスポンシブUIの基本パターン

読みやすい本文幅と余白を作る

画面いっぱいに本文を広げず、最大幅と左右の余白を組み合わせます。

<main className="mx-auto w-full max-w-screen-lg px-4 sm:px-6 lg:px-8">
  {children}
</main>

w-fullで利用可能な幅を使い、max-w-screen-lgで広がり過ぎを防ぎます。

左右の余白は小さい画面のpx-4を基準にして、画面が広がるにつれて増やします。

カード一覧の列数を段階的に増やす

カード一覧は、小さい画面で一列にしておくと内容を読みやすく保てます。

<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
  {items.map((item) => (
    <Card key={item.id} item={item} />
  ))}
</div>

md:grid-cols-2とlg:grid-cols-3は、指定した幅以上で列数を増やします。

端末名で分けるのではなく、内容が窮屈になる位置を見ながらブレークポイントを選ぶと、画面幅が中間の端末にも対応しやすくなります。

折り返す要素にはgapを使う

ナビゲーションやボタン群を折り返す場合は、行と列の両方に間隔を付けられるgapが扱いやすい指定です。

<nav aria-label="主要メニュー" className="flex flex-wrap justify-center gap-4">
  <a className="text-blue-700 underline-offset-4 hover:underline" href="/">ホーム</a>
  <a className="text-blue-700 underline-offset-4 hover:underline" href="/guide">ガイド</a>
</nav>

リンク間の見た目だけでなく、キーボードで移動したときの順序が内容の順序と一致しているかも確認します。

ダッシュボードだけに共通レイアウトを適用する

app/dashboard/layout.tsxを置くと、ダッシュボード配下のページだけにサイドバーと本文領域を適用できます。

import Sidebar from "@/components/Sidebar"

export default function DashboardLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <div className="min-h-screen lg:grid lg:grid-cols-[16rem_1fr]">
      <aside className="hidden border-r lg:block">
        <Sidebar />
      </aside>
      <main className="min-w-0 p-4 sm:p-6">{children}</main>
    </div>
  )
}

hidden lg:blockは大きい画面だけにサイドバーを表示します。

ただし、モバイルでサイドバーを隠すなら、同じ移動先へ到達できるメニューボタンなどの代替手段が必要です。

開閉式メニューを実装するときは、ボタンのaria-expandedを実際の開閉状態と同期させ、操作後にフォーカスが不自然な場所へ移らないことを確認します。

画像の幅と代替テキストを整える

Next.jsのImageコンポーネントでは、表示領域の比率を保つためのwidthとheightに加え、レスポンシブ表示ではsizesも指定します。

import Image from "next/image"

<Image
  src="/hero.jpg"
  alt=""
  width={1200}
  height={600}
  sizes="(max-width: 1024px) 100vw, 1024px"
  className="h-auto w-full"
/>

この例のaltが空なのは、画像を装飾として扱う場合です。

画像が操作方法や内容を伝えるなら、空にせず、画像を見られない読者にも同じ要点が伝わる代替テキストへ置き換えます。

sizesは、画面幅ごとに画像が占めるおおよその幅をブラウザーへ伝える指定です。

実際のCSSと合っていない値を入れると、必要以上に大きな画像が選ばれることがあるため、コンテナ幅を変えたら一緒に見直します。

アクセシビリティを実装とテストに含める

レスポンシブ対応は、要素を画面内へ収めるだけでは完了しません。

キーボード、拡大表示、読み上げなど、異なる操作方法でも内容と機能へ到達できるかを確認します。

キーボードフォーカスを見える状態にする

<button className="rounded bg-blue-700 px-4 py-2 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-700 focus-visible:ring-offset-2">
  設定を保存
</button>

focus-visibleは、主にキーボード操作でフォーカスされたときの見た目を指定します。

既定のアウトラインを消す場合は、同時に十分見分けられるリングなどを用意し、背景色が変わっても見失わないかを確認します。

表示を隠しても機能は失わせない

hiddenで非表示にした要素は操作対象からも外れるため、ナビゲーションや検索などの機能を隠すときは、小さい画面向けの代替UIを用意します。

アイコンだけのボタンには、見た目だけでは分からない操作名をテキストまたはアクセシブルな名前として与えます。

より広い確認項目は、モバイルアクセシビリティとレスポンシブ設計の実践ガイドで確認できます。

公開前の確認手順

  1. 小さい画面を基準に、見出し、本文、操作要素の順序を確認します。
  2. 幅を少しずつ広げ、横スクロール、文字の重なり、不自然な余白が生じる位置を探します。
  3. TabキーとShift+Tabキーで、すべてのリンクとボタンへ順番に移動します。
  4. メニューを開閉し、状態、フォーカス、閉じた後の操作が一致するかを確認します。
  5. 画像の用途に応じて、代替テキストが必要か、空のaltが適切かを判断します。
  6. 実機または複数の入力方法で確認し、開発者ツールの画面幅変更だけでは見つからない使いにくさを探します。

実装の要点

レスポンシブUIは、接頭辞のないクラスで小さい画面の基本形を作り、必要な位置だけmd:やlg:で上書きすると整理しやすくなります。

App Routerのネストしたレイアウトを使えば、サイト全体の共通部分と特定の画面だけに必要なUIを分けられます。

画像のsizes、代替テキスト、キーボードフォーカス、非表示要素の代替手段まで確認して、見た目と操作の両方を整えてください。

UIからAPI連携へ進む場合は、Next.jsとFastAPIの連携設計も次の資料として利用できます。

この記事に関連する株式会社greedenの取り組み

レスポンシブUIは、実装後も端末や入力方法を変えて検証し、改善を重ねる必要があります。株式会社greedenは、Next.jsを含むWebシステム開発で、要件整理から設計、実装、テスト、保守改善まで一貫して支援します。

モバイルバージョンを終了