この記事に関して
shadcn/ui を利用した場合の ダークモード を調査しました。
純粋にダークモードを使いたいだけならば、Chrome 拡張機能である ↓ Dark Reader を使ってもらうのが簡単です。
Next.js 13 App Router への対応は?
Next.js 13 App Router 使用時にはまだ問題があるようです。
Next.js 13 appDir support · Issue #152 · pacocoursey/next-themes
対処方法は プロパティ suppressHydrationWarning を利用します。
その他に mount 状態をチェックします。
詳しくは ↑Issue を御覧ください。
リポジトリ
第 1 部のコード
masakinihirota/shadcn_darkmode
※この記事とリポジトリとのコードに差分がもしあった場合、リポジトリのコードが正しいものとなります。
やること
公式ドキュメントを読む。
shadcn/ui 公式ドキュメント
Dark Mode - shadcn/ui
- Next.js に shadcn/ui でダークモードを追加する
shadcn/ui を調査する。
ダークモードが成功したら
- 開発用テンプレートにダークモードを追加する
Next.js app router 開発用テンプレート (Storybook Supabase shadcn/ui) - Qiita
環境
Windows10
VSCode
Next.js 13 App Router
shadcn/ui
基本的な shadcn/ui コマンド
初期化
npx shadcn-ui@latest init
※初期化コマンドは既存設定ファイルがある場合上書きされます。
CLI からのコンポーネントインストール方法
npx @shadcn/ui add
npx @shadcn-ui add [options] [components...]
※addコマンドは既存コンポーネントファイルがあっても上書きされます。(確認)
options:
-y, --yes skip confirmation prompt. (default: false)
-o, --overwrite overwrite existing files. (default: false)
-c, --cwd <cwd> the working directory. defaults to the current directory.
-p, --path <path> the path to add the component to.
-h, --help display help for command
第1部 Next.js + shadcn/ui ダークモード
はじめに - shadcn/ui
shadcn/ui はコンポーネントライブラリではありません、
コンポーネントのコレクションです。
必要なコンポーネントを選択します。
コードをコピーしてプロジェクトに貼り付け、
ニーズに合わせてカスタマイズします。
CLI からでもインストールできます。
インストール - shadcn/ui
Next.js を選択
Next.js - shadcn/ui
Next.js のインストール
npx create-next-app@latest shadcn_darkmode --typescript --tailwind --eslint
TailwindCSS
src\app\globals.css
このファイルの配置を
src\styles\globals.css
へ移動します。
コマンド
New-Item -ItemType Directory -Path "src\styles"
Move-Item -Path "src\app\globals.css" -Destination "src\styles\globals.css"
globals.css ファイルから、デフォルトのインストール画面で必要だった CSS 部分を削除し、TailwindCSS で必要な部分だけを残します。
@tailwind base;
@tailwind components;
@tailwind utilities;
globals.css ファイルを読み込んでいるファイルの path を修正します。
import '@/styles/globals.css'
...
<html lang='ja'>
ja と日本語にも設定しておきます。
shadcn の初期化
npx shadcn-ui@latest init
global CSS のファイルの場所を指定します。
src/styles/globals.css
初期化が終了すると、components.json というファイルが作成されます。
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/styles/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
ソース構造(参考)
src
├── app
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── ui
│ │ ├── alert-dialog.tsx
│ │ ├── button.tsx
│ │ ├── dropdown-menu.tsx
│ │ └── ...
│ ├── main-nav.tsx
│ ├── page-header.tsx
│ └── ...
├── lib
│ └── utils.ts
├── styles
│ └── globals.css
├── next.config.js
├── package.json
├── postcss.config.js
├── tailwind.config.js
└── tsconfig.json
-
UI コンポーネントは components/ui フォルダに配置しています。
-
やなどの残りのコンポーネントは、components フォルダに配置します。
-
lib フォルダにはすべてのユーティリティ関数が入っています。utils.ts には cn ヘルパーを定義しています。
-
styles フォルダにはグローバル CSS が入っています。
ボタンを追加してみる
コンポーネントのインストール
npx shadcn-ui@latest add
button コンポーネントを選択します。
インストール場所
src\components\ui
インストールされたファイル
src\components\ui\button.tsx
ボタンを表示するコンポーネントを作成します。
// import文の順番も自動整形されます。
import { type NextPage } from "next";
import Link from "next/link";
import { Button } from "@/components/ui/button";
const Home: NextPage = () => {
return (
<main>
{/* TailwindCSSのプロパティ値も自動整列されます。 */}
<h1 className="p-4 pt-2">VNS.BLUE</h1>
Next.js app router 開発用テンプレート (Storybook Supabase shadcn/ui)
<br />
{/* ↓この行を消すとimport文が自動削除されます。 */}
<Link href="/">Home</Link>
<div>
<Button>shadcn/ui の Button</Button>
</div>
<br />
<Link href="./ModeTogglePage">ModeTogglePage</Link>
</main>
);
};
export default Home;
ボタンコンポーネントテストページ
import { Button } from "@/components/ui/button"
export default function Home() {
return (
<div>
<Button>Click me</Button>
</div>
)
}
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true, <<< TypeScriptを使う場合はtrue
"tailwind": {
"config": "tailwind.config.js",
"css": "src/styles/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
Theming テーマ設定
Theming - shadcn/ui
テーマ設定に CSS 変数 または TailwindCSS を使用します。
CSS 変数を使用する、
もしくは
Tailwind CSS の ユーティリティクラス を使用するかを選択できます。
↓ ユーティリティクラス の例
ユーティリティクラス を使用する場合の設定
{
"style": "default",
"rsc": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": false <<< false に設定します。
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
CSS 変数 の例
CSS 変数 を使用する場合の設定
{
"style": "default",
"rsc": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true <<< true に設定します。
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
Convention 規約
色に関しては、シンプルな背景と前景のコンベンションを使用しています。
前景変数はテキスト色に使用されます。
背景変数はコンポーネントの背景色に使用されます。
変数がコンポーネントの背景色に使用される場合、背景接尾辞は省略されます。
例えば、--primary-background という変数は、コンポーネントの背景色に使用する場合は、--primary として省略して使用することができます。
例えば、次のような CSS 変数が与えられます。
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
※ --primary-background は省略され--primary になっています。
--primary とは?
「--primary」は、カスタマイズ可能な CSS 変数の一つで、プロジェクトで使用される主要な色を表します。
この変数は、コンポーネントの背景色やテキスト色など、プロジェクト内の様々な要素の色に使用されます。
例えば、以下のようにして「--primary」変数を使用して、背景色が変更された div 要素を作成することができます。
div {
background-color: var(--primary);
}
「The background suffix」とは、前述の通り、背景色に使用される CSS 変数名において、接尾辞として使用される文字列のことを指します。例えば、変数名が「--primary」である場合、背景色に使用する場合は「--primary」とだけ指定し、接尾辞は省略されます。一方、テキスト色に使用する場合は、変数名に「-foreground」などの接尾辞を付けることが一般的です。このように、接尾辞を使用することで、変数名からどのような色が使用されるかを容易に理解することができます。
↑↑ 先程の ↓ この数値は
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
このように使われます。
このコンポーネントの背景色は hsl(var(--primary))、前景色は hsl(var(--primary-foreground))となります。
CSS 変数の値は、HSL 色空間で定義されています。HSL 色空間は、色相(Hue)、彩度(Saturation)、明度(Lightness)の 3 つの値を使用して色を表現します。HSL 色空間は、RGB 色空間よりも直感的に理解しやすいため、CSS 変数の値を HSL 色空間で定義しています。
HSL とは
HSL は、色相(Hue)、彩度(Saturation)、明度(Lightness)の 3 つの値を使用して色を表現する色空間の一つです。
色相は、色の種類を表し、0 度から 360 度までの値で表されます。
彩度は、色の鮮やかさを表し、0%から 100%までの値で表されます。
明度は、色の明るさを表し、0%から 100%までの値で表されます。
例えば、赤色は色相が 0 度、彩度が 100%、明度が 50%となります。
CSS 変数の値を HSL 色空間で定義することで、色相、彩度、明度の値を直接指定することができます。
List of variables
カスタマイズ可能な CSS 変数の一覧
背景色
Default background color of <body />...その他
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
薄い色調の背景色
Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch />
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
カードの背景色
Background color for <Card />
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
ユーザーがマウスオーバーしたときに表示される背景色
Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover />
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
デフォルトのボーダー色
Default border color
--border: 214.3 31.8% 91.4%;
入力フィールドに使用されるボーダー色
Border color for inputs such as <Input />, <Select />, <Textarea />
--input: 214.3 31.8% 91.4%;
プライマリカラー
Primary colors for <Button />
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
セカンダリカラー
Secondary colors for <Button />
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
ホバーエフェクトなどのアクセントに使用されるカラー
Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
「アクセントカラー」とは、UIの中で特定の要素に対して強調するために使用されるカラーを指します。例えば、ボタンやリンクなどのアクション要素に使用されます。アクセントカラーは、Primary colorsやSecondary colorsとは異なり、UIの中で特定の要素に対して強調するために使用されるため、視覚的に目立つ色が使用されることが多いです。プロジェクトによっては、Primary colorsとSecondary colorsに加えて、アクセントカラーとして「Third colors」を定義する場合があります。
削除やキャンセルなどの破壊的なアクションに使用されるカラー
Used for destructive actions such as <Button variant="destructive">
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
フォーカスリングに使用されるカラー
Used for focus ring
--ring: 215 20.2% 65.1%;
フォーカスリングは、ユーザーがキーボードでフォーカスを移動したときに表示される、要素の周りに表示されるリング状のエフェクトです。
要素の角を丸めるために使用されるCSSプロパティ
Border radius for card, input and buttons
--radius: 0.5rem;
カード、入力フィールド、ボタンなどの要素に適用される角丸の半径
新しい色の追加
新しい色を追加するには、CSS ファイル と tailwind.config.js ファイル に追加する必要があります。
app/globals.css
:root {
--warning: 38 92% 50%;
--warning-foreground: 48 96% 89%;
}
.dark {
--warning: 48 96% 89%;
--warning-foreground: 38 92% 50%;
}
tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
warning: "hsl(var(--warning))",
"warning-foreground": "hsl(var(--warning-foreground))",
},
},
},
}
これで、↓ コンポーネントで警告ユーティリティ・クラスを使用できるようになりました。
その他のカラーフォーマット
テーマ設定には HSL カラーを使用することをお勧めしますが、お好みにより他のカラーフォーマットを使用することもできます。
rgb、rgba、または hsl カラーの使用に関する詳細は、Tailwind CSS ドキュメントを参照してください。
ダークモード
Dark Mode - shadcn/ui
Create a theme provider テーマプロバイダーの作成
next-themes
next-themes - npm
デモ画面
Next.js アプリのテーマを抽象化。
- 2 行のコードで完璧なダークモードを実現
- prefers-color-scheme によるシステム設定
- カラースキームを使ったテーマブラウザ UI
- 読み込み時にフラッシュなし(SSR と SSG の両方)
- タブとウィンドウ間でテーマを同期
- テーマ変更時のフラッシュを無効化
- 特定のテーマにページを強制的に合わせる
- クラスまたはデータ属性セレクタ
- useTheme フック
インストール
npm install next-themes
touch src\components\theme-provider.tsx
テーマプロバイダーの作成
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
import * as React from "react";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
ルートレイアウトにラップする
src\app\layout.tsx
import "@/styles/globals.css";
import { Inter } from "next/font/google";
import { ThemeProvider } from "next-themes";
import React from "react";
import type { Metadata } from "next";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ja" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}
※ suppressHydrationWarning は、React がクライアント側でレンダリングされたコンテンツをハイドレーションすることを抑制するために使用されます。
これで Next.js 13 App Router 使用時にダークモードを使用してもハイドレーションエラーが回避できると報告されています。
【Next.js 13】 next-themes でダークモードを実装する方法 | logsuke
テーマを切り替える モードトグルを作成する
ModeTogglePage フォルダを作ります。
src\app\ModeTogglePage\page.tsx ファイルを作ります。
インストール
npx shadcn-ui@latest add dropdown-menu
import Link from "next/link"
export default function Home() {
return (
<main>
<h1>VNS.BLUE</h1>
<Link href="./ButtonTestPage">ButtonTestPage</Link>
<Link href="./ModeTogglePage">ModeTogglePage</Link>
</main>
)
}
※↑ 各機能へのリンクを追加しました。
"use client";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="default">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
npm run dev
でローカルサーバーを起動して動作確認をします。
切り替えが確認できれば作業は完了です。
第2部 開発用テンプレート + shadcn/ui ダークモード
第1部で調べた内容を↓このリポジトリに組み込みました。
masakinihirota/next13apptemplate
↓この記事の一部を切り出したものになります。
Next.js app router 開発用テンプレート (Storybook Supabase shadcn/ui) - Qiita
第3部 ダークモードの調査
Tailwind CSS を使った ダークモード実装の効率的なアプローチ
Tailwind CSS のダークモード対応ガイド - snappify.com
まとめ
以上を踏まえ、冒頭で紹介した CSS 変数と カスタムカラーを使うアプローチが最適解だという結論に至りました。このアプローチは最近海外で注目を集めている shadcn/ui で知りました。 shadcn/ui をつかうとよくある変数の定義とカスタムカラーの紐付けをプロジェクトに反映することができるので興味のある方は試してみてください。
ライブラリ next-themes について
next-themes - npm
簡単な解説
↑ このように HTML に dark クラスを追加すると、
↓ このように TailwindCSS の dark モードが有効になります。
インストール
npm install next-themes
App Router での利用時の問題点
Next.js 13 appDir support · Issue #152 · pacocoursey/next-themes
https://github.com/pacocoursey/next-themes/issues/152
shadcn/ui その他
CLI
CLI - shadcn/ui
https://ui.shadcn.com/docs/cli
(実験的コマンド)
↓ 更新が利用可能なコンポーネントのリストを取得します。
npx shadcn-ui diff
↓ diff [component] コマンドで変更の確認します。
npx shadcn-ui diff alert
タイポグラフィ 装飾の使い方
タイポグラフィ - shadcn/ui
https://ui.shadcn.com/docs/components/typography
Figma
@shadcn/ui - Design System – Figma
https://www.figma.com/community/file/1203061493325953101
参考
【Next.js 13】 next-themes でダークモードを実装する方法 | logsuke
https://logsuke.com/web/programming/react/next-themes
※この記事には、ハイドレーションエラー対策があります。
Next × Stitches × next-themes を使用してダークモード実装
https://zenn.dev/hiro4hiro4/articles/e90778a816df84
Tailwind CSS を使った ダークモード実装の効率的なアプローチ