---
# System prepended metadata

title: 整合專案開發，完成全端網站應用

---



# 學習使用 Chakra UI 設計網站


## 課程目標

本教學將帶領學生從零開始建立一個整合 Chakra UI 的 Next.js 專案，學習：

- Next.js Pages Router 架構
- TypeScript 配置與型別系統
- Chakra UI v3 整合與自定義主題
- 深色模式實作
- 解決 SSR Hydration 問題


## 一、建立 Next.js 與 Chakra UI 初始專案

### 1.1 初始化專案

使用 `create-next-app` 建立專案：

```bash
npx create-next-app@latest 2025-fullstack-course-forntend
```

[範例專案](https://github.com/IffyArt/2025-fullstack-course-forntend/tree/feature/chakra-ui)

#### 課程使用配置
```bash
✔ What is your project named? … my-app
✔ Would you like to use the recommended Next.js defaults? › No, customize settings
✔ Would you like to use TypeScript? … Yes
✔ Which linter would you like to use? › ESLint
✔ Would you like to use React Compiler? … Yes
✔ Would you like to use Tailwind CSS? … No
✔ Would you like your code inside a `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … No
✔ Would you like to use Turbopack? (recommended) … No
✔ Would you like to customize the import alias (`@/*` by default)? … Yes
✔ What import alias would you like configured? … @/*
```

#### 安裝套件


```bash
npm i @chakra-ui/react @emotion/react
```
> v3 已精簡 peer deps；一般情況不需要再手動安裝 @emotion/styled 或 framer-motion。

<br/>

### 1.2 建立並使用 Provider
v3 推薦用官方 snippet，會幫你放好 Provider：

```bash
npx @chakra-ui/cli snippet add provider
```

這個指令會在你的專案（通常 `src/components/ui/`）生成 `provider.tsx`，裡面已經接妥 v3 所需設定。

#### 設置框架自訂義設定

```bash=
src/
├── pages/                   # Next.js 頁面路由
│   ├── _app.tsx             # 應用程式根組件（全局 Provider）
│   ├── _document.tsx        # HTML 文檔結構
│   └── index.tsx            # 首頁範例
│
├── components/              # React 組件
│   └── ui/                  # Chakra UI 相關組件
│       └── provider.tsx     # ChakraProvider 配置
│
├── fixtures/                # 配置和固定資料
│   └── theme/               # Chakra UI 主題配置
│       ├── index.ts         # 主題系統整合
│       ├── colors.ts        # 自定義顏色定義
│       └── layer-styles.ts  # Layer Styles 定義
└──                          # 其他源碼目錄
```

---

**`src/fixtures/theme/color.ts`** - 可以覆蓋 chakra 中的色票設置 

```ts=
const colors = {
  blue: {
    100: { value: '#e0f2fe' },
    200: { value: '#bae6fd' },
    300: { value: '#7dd3fc' },
    400: { value: '#38bdf8' },
    500: { value: '#0ea5e9' },
    600: { value: '#0284c7' },
    700: { value: '#0369a1' },
    800: { value: '#075985' },
    900: { value: '#0c4a6e' },
  },
};

export default colors;
```
> 這裡僅為教學範例，色彩設定的部分詳情可至官網查詢 [Chakra UI Colors](https://www.chakra-ui.com/docs/theming/colors)
---

**`src/fixtures/theme/layer-styles.ts`** - 可以定義一些能重複使用的組合樣式
```ts=
import { defineLayerStyles } from '@chakra-ui/react';

const layerStyles = defineLayerStyles({
  // border styles
  'border-solid-top': {
    description: 'border top solid',
    value: {
      borderTop: '1px solid',
      borderColor: 'gray.200',
    },
  },
});

export default layerStyles;
```

---

**`src/fixtures/theme/index.ts`** - 建立框架的設定配置
```ts=
import {
  createSystem,
  defaultConfig,
  defineConfig,
  SystemConfig,
  SystemContext,
} from '@chakra-ui/react';
import colors from './colors';
import layerStyles from './layer-styles';

const config = defineConfig({
  theme: {
    layerStyles,
    tokens: {
      colors,
    },
  },
}) as SystemConfig;

export const customSystemConfig: SystemContext = createSystem(
  defaultConfig,
  config,
);
```

---

**`src/components/ui/provider.tsx`** - 最後將設定配置引入 provider.tsx 中

```tsx=
import { customSystemConfig } from '@/fixtures/theme';
import { ChakraProvider } from '@chakra-ui/react';
import { ColorModeProvider, type ColorModeProviderProps } from './color-mode';

export function Provider(props: ColorModeProviderProps) {
  return (
    <ChakraProvider value={customSystemConfig}>
      <ColorModeProvider {...props} />
    </ChakraProvider>
  );
}
```


<br/>

### 1.3 建立 Color Mode 組件
以下的指令會自動在 `src/components/ui/` 新增 `color-mode.tsx` 的檔案，並同時完成基礎設置（含 `next-themes` 的 ColorModeProvider）。
```bash
npx @chakra-ui/cli snippet add color-mode
```

#### `src/components/ui/` 底下會自動多出檔案

```bash=
src/
├── pages/                   # Next.js 頁面路由
│   ├── _app.tsx             # 應用程式根組件（全局 Provider）
│   ├── _document.tsx        # HTML 文檔結構
│   └── index.tsx            # 首頁範例
│
├── components/              # React 組件
│   └── ui/                  # Chakra UI 相關組件
│       ├── provider.tsx     # ChakraProvider 配置
│       └── color-mode.tsx   # 深色模式組件和 Hooks
│
├── fixtures/                # 配置和固定資料
│   └── theme/               # Chakra UI 主題配置
│       ├── index.ts         # 主題系統整合
│       ├── colors.ts        # 自定義顏色定義
│       └── layer-styles.ts  # Layer Styles 定義
└──                          # 其他源碼目錄
```

#### 如若有設置比較嚴格的 typescript 檢查機制，可先加上註解來處理錯誤訊息

```tsx=
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
```

安裝下來的檔案會使用繼承的方式讓我們方便進行擴充，但是在比較嚴格的檢查機制下，他將會因為沒有給予內容而報出錯誤訊息

```tsx=
import type { IconButtonProps, SpanProps } from '@chakra-ui/react';
import { ClientOnly, IconButton, Skeleton, Span } from '@chakra-ui/react';
import type { ThemeProviderProps } from 'next-themes';
import { ThemeProvider, useTheme } from 'next-themes';
import * as React from 'react';
import { LuMoon, LuSun } from 'react-icons/lu';

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ColorModeProviderProps extends ThemeProviderProps {}

export function ColorModeProvider(props: ThemeProviderProps) {
  return (
    <ThemeProvider attribute='class' disableTransitionOnChange {...props} />
  );
}

export type ColorMode = 'light' | 'dark';

export interface UseColorModeReturn {
  colorMode: ColorMode;
  setColorMode: (colorMode: ColorMode) => void;
  toggleColorMode: () => void;
}

export function useColorMode(): UseColorModeReturn {
  const { resolvedTheme, setTheme, forcedTheme } = useTheme();
  const colorMode = forcedTheme || resolvedTheme;
  const toggleColorMode = () => {
    setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
  };
  return {
    colorMode: colorMode as ColorMode,
    setColorMode: setTheme,
    toggleColorMode,
  };
}

export function useColorModeValue<T>(light: T, dark: T) {
  const { colorMode } = useColorMode();
  return colorMode === 'dark' ? dark : light;
}

export function ColorModeIcon() {
  const { colorMode } = useColorMode();
  return colorMode === 'dark' ? <LuMoon /> : <LuSun />;
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface ColorModeButtonProps extends Omit<IconButtonProps, 'aria-label'> {}

export const ColorModeButton = React.forwardRef<
  HTMLButtonElement,
  ColorModeButtonProps
>(function ColorModeButton(props, ref) {
  const { toggleColorMode } = useColorMode();
  return (
    <ClientOnly fallback={<Skeleton boxSize='9' />}>
      <IconButton
        onClick={toggleColorMode}
        variant='ghost'
        aria-label='Toggle color mode'
        size='sm'
        ref={ref}
        {...props}
        css={{
          _icon: {
            width: '5',
            height: '5',
          },
        }}
      >
        <ColorModeIcon />
      </IconButton>
    </ClientOnly>
  );
});

export const LightMode = React.forwardRef<HTMLSpanElement, SpanProps>(
  function LightMode(props, ref) {
    return (
      <Span
        color='fg'
        display='contents'
        className='chakra-theme light'
        colorPalette='gray'
        colorScheme='light'
        ref={ref}
        {...props}
      />
    );
  },
);

export const DarkMode = React.forwardRef<HTMLSpanElement, SpanProps>(
  function DarkMode(props, ref) {
    return (
      <Span
        color='fg'
        display='contents'
        className='chakra-theme dark'
        colorPalette='gray'
        colorScheme='dark'
        ref={ref}
        {...props}
      />
    );
  },
);
```

> **重點說明**：
>
> - `ColorModeProvider`: 整合 `next-themes` 的主題管理
> - `useColorMode`: 提供色彩模式狀態和切換函數的 Hook
> - `ColorModeButton`: 使用 `ClientOnly` 包裝，避免 SSR hydration 問題
> - `ClientOnly`: 確保組件只在客戶端渲染，防止服務端與客戶端狀態不一致

<br/>


### 1.4 編輯 \_document.tsx

編輯 `src/pages/_document.tsx`：

```tsx=
import { Head, Html, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang='zh' suppressHydrationWarning>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}
```

> **重點**：
>
> - `lang='zh'`: 設定頁面語言為中文
> - `suppressHydrationWarning`: 抑制 hydration 警告（因為深色模式會在客戶端更新 class）



<br/>


### 1.5 編輯 \_app.tsx

編輯 `src/pages/_app.tsx`：

```tsx=
import { Provider } from '@/components/ui/provider';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <Provider>
      <Component {...pageProps} />
    </Provider>
  );
}
```

> **說明**：`_app.tsx` 包裝所有頁面組件，提供全局 Provider。

<br/>

### 1.6 編輯 index.tsx

編輯 `src/pages/index.tsx`：

```tsx=
import { ColorModeButton } from '@/components/ui/color-mode';
import { Button } from '@chakra-ui/react';

export default function Home() {
  return (
    <>
      <ColorModeButton />
      <Button colorPalette='blue'>按鈕</Button>
    </>
  );
}
```

> **說明**：
>
> - 使用 `ColorModeButton` 提供深色模式切換按鈕
> - 使用自定義的 `blue` 色票（定義在 `colors.ts`）


<br/>

### 1.7 執行與測試

#### 啟動開發伺服器

```bash
npm run dev
```

#### 開啟瀏覽器

前往 [http://localhost:3000](http://localhost:3000)

你應該會看到：

- ☀️/🌙 深色模式切換按鈕
- 藍色的按鈕（使用自定義色票）

#### 測試功能

1. **測試深色模式**：點擊切換按鈕，觀察主題切換
2. **測試 SSR**：重新整理頁面，確認沒有 hydration 錯誤
3. **檢查控制台**：確認沒有警告或錯誤

<br/>

### 1.8 進階擴充
[範例專案](https://github.com/IffyArt/2025-fullstack-course-forntend/tree/feature/chakra-ui-expand)
#### 擴展 1: 新增更多顏色

編輯 `src/fixtures/theme/colors.ts`：

```tsx=
const colors = {
  blue: {
    /* ... */
  },
  green: {
    500: { value: '#10b981' },
    600: { value: '#059669' },
  },
  red: {
    500: { value: '#ef4444' },
    600: { value: '#dc2626' },
  },
};
```

使用：

```tsx=
<Button colorPalette='green'>綠色按鈕</Button>
<Button colorPalette='red'>紅色按鈕</Button>
```

#### 擴展 2: 新增 Layer Styles

編輯 `src/fixtures/theme/layer-styles.ts`：

```tsx=
const layerStyles = defineLayerStyles({
  'border-solid-top': {
    /* ... */
  },
  'card-elevated': {
    description: '卡片樣式',
    value: {
      padding: '4',
      borderRadius: 'md',
      boxShadow: 'lg',
      bg: 'white',
      _dark: {
        bg: 'gray.800',
      },
    },
  },
});
```

使用：

```tsx=
<Box layerStyle='card-elevated'>卡片內容</Box>
```

#### 擴展 3: 新增更多頁面

建立 `src/pages/about.tsx`：

```tsx=
import { Box, Heading } from '@chakra-ui/react';

export default function About() {
  return (
    <Box p={8}>
      <Heading>關於頁面</Heading>
    </Box>
  );
}
```

前往 [http://localhost:3000/about](http://localhost:3000/about) 查看。


<br/>

## 二、Chakra UI 常用組件

### 佈局組件

```tsx=
import { Box, Container, Flex, Grid, Stack, VStack, HStack } from '@chakra-ui/react';

// Box - 最基本的佈局組件
<Box padding={4} bg="gray.100">內容</Box>

// Container - 固定寬度容器
<Container maxW="container.lg">內容</Container>

// Flex - 彈性佈局
<Flex justify="space-between" align="center">
  <Box>左側</Box>
  <Box>右側</Box>
</Flex>

// Grid - 網格佈局
<Grid templateColumns="repeat(3, 1fr)" gap={6}>
  <Box>項目 1</Box>
  <Box>項目 2</Box>
  <Box>項目 3</Box>
</Grid>

// Stack - 堆疊佈局
<VStack gap={4}>  {/* 垂直 */}
  <Box>項目 1</Box>
  <Box>項目 2</Box>
</VStack>

<HStack gap={4}>  {/* 水平 */}
  <Box>項目 1</Box>
  <Box>項目 2</Box>
</HStack>
```

### 文字組件

```tsx=
import { Text, Heading } from '@chakra-ui/react';

// Heading
<Heading as="h1" size="2xl">大標題</Heading>
<Heading as="h2" size="xl">次標題</Heading>

// Text
<Text fontSize="lg" fontWeight="bold">粗體文字</Text>
<Text color="gray.600">灰色文字</Text>
```

### 互動組件

```tsx=
import { Button, Input, Textarea, Select, Checkbox, Switch } from '@chakra-ui/react';

// Button
<Button colorPalette="blue">
  按鈕
</Button>

<Button variant="outline">輪廓按鈕</Button>
<Button variant="ghost">幽靈按鈕</Button>

// Input
<Input placeholder="請輸入..." />

// Textarea
<Textarea placeholder="請輸入多行文字..." />

// Checkbox
<Checkbox.Root>
  <Checkbox.HiddenInput />
  <Checkbox.Control />
  <Checkbox.Label>記住我</Checkbox.Label>
</Checkbox.Root>

// Switch
<Switch.Root>
  <Switch.HiddenInput />
  <Switch.Control />
  <Switch.Label />
</Switch.Root>
```

### 資料展示組件

```tsx=
import { Card, Badge, Tag, Avatar, Image } from '@chakra-ui/react';

// Card (Chakra UI v3)
<Card.Root>
  <Card.Header>
    <Heading size="md">卡片標題</Heading>
  </Card.Header>
  <Card.Body>
    <Text>卡片內容</Text>
  </Card.Body>
  <Card.Footer>
    <Button>動作</Button>
  </Card.Footer>
</Card.Root>

// Badge
<Badge colorPalette="green">新</Badge>

// Avatar
<Avatar.Root>
  <Avatar.Fallback name='Oshigaki Kisame' />
  <Avatar.Image src='https://bit.ly/broken-link' />
</Avatar.Root>


// Image
<Image
  src='https://i.pravatar.cc/400?u=1'
  boxSize='150px'
  borderRadius='full'
  fit='cover'
  alt='Naruto Uzumaki'
/>
```

<br/>

## 三、Chakra UI Props 速查

### 間距（Spacing）

```tsx=
// padding
<Box p={4}>Box</Box>         {/* 所有方向 */}
<Box px={4}>Box</Box>        {/* 水平 (left + right) */}
<Box py={4}>Box</Box>        {/* 垂直 (top + bottom) */}
<Box pt={4}>Box</Box>        {/* top */}
<Box pb={4}>Box</Box>        {/* bottom */}
<Box pl={4}>Box</Box>        {/* left */}
<Box pr={4}>Box</Box>        {/* right */}

// margin (同樣的邏輯)
<Box m={4} mx={4} my={4} mt={4} mb={4} ml={4} mr={4} />
```

**數值對照**：

- `1` = 0.25rem (4px)
- `2` = 0.5rem (8px)
- `4` = 1rem (16px)
- `8` = 2rem (32px)

### 顏色（Colors）

```tsx=
// 文字顏色
<Text color="blue.500">藍色文字</Text>
<Text color="gray.600">灰色文字</Text>

// 背景顏色
<Box bg="blue.50">淺藍背景</Box>
<Box bgColor="red.500">紅色背景</Box>

// 邊框顏色
<Box borderColor="gray.200" borderWidth={1}><Text>灰色邊框</Text></Box>
```

### 尺寸（Size）
```tsx=
// width
<Box w="100%">        {/* 100% */}
<Box w="50vw">        {/* 50% viewport width */}
<Box w="200px">       {/* 固定寬度 */}
<Box maxW="500px">    {/* 最大寬度 */}

// height
<Box h="100vh">       {/* 100% viewport height */}
<Box h="200px">       {/* 固定高度 */}
<Box minH="300px">    {/* 最小高度 */}
```
> 僅範例

### 響應式設計

```tsx=
// 陣列語法（mobile, tablet, desktop）
<Box w={["100%", "80%", "60%"]}>

// 物件語法
<Box
  w={{ base: "100%", md: "80%", lg: "60%" }}
  fontSize={{ base: "sm", md: "md", lg: "lg" }}
>

// Breakpoints
// base: 0px
// sm: 480px
// md: 768px
// lg: 992px
// xl: 1280px
// 2xl: 1536px
```
> 僅範例

<br/>

## 四、使用深色模式

### useColorMode

```tsx=
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';

function MyComponent() {
  const { colorMode, toggleColorMode } = useColorMode();

  return <Button onClick={toggleColorMode}>目前模式: {colorMode}</Button>;
}
```

### useColorModeValue

```tsx=
function MyComponent() {
  const bg = useColorModeValue('white', 'gray.800');
  const color = useColorModeValue('black', 'white');

  return (
    <Box bg={bg} color={color}>
      內容
    </Box>
  );
}
```

### CSS Props 方式

```tsx=
<Box
  bg='white'
  _dark={{ bg: 'gray.800' }}
  color='black'
  _dark={{ color: 'white' }}
>
  會根據模式自動切換
</Box>
```

### 使用 ClientOnly 處理 SSR 混合問題

```tsx=
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';
import { Box, Button, ClientOnly } from '@chakra-ui/react';

export default function Home() {
  const { colorMode, toggleColorMode } = useColorMode();

  const bg = useColorModeValue('white', 'gray.800');
  const color = useColorModeValue('black', 'white');

  return (
    <ClientOnly fallback={<div>Loading...</div>}>
      <Box bg={bg} color={color}>
        <Button onClick={toggleColorMode}>
          切換至 {colorMode === 'light' ? '深色' : '淺色'} 模式
        </Button>
      </Box>
    </ClientOnly>
  );
}
```


<br/>

## 五、導航列設計練習

[範例專案](https://github.com/IffyArt/2025-fullstack-course-forntend/tree/feature/chakra-ui-navbar)


新增 Navbar.tsx

在組件以下新增導覽列檔案 `src/components/Navbar.tsx`：
```tsx=
import { ColorModeButton } from '@/components/ui/color-mode';
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
import { useRouter } from 'next/router';

const Navbar = () => {
  const router = useRouter();

  return (
    <Box
      as='nav'
      bg='white'
      borderBottom='1px'
      borderColor='gray.200'
      _dark={{ bg: 'gray.800', borderColor: 'gray.700' }}
      px={4}
      py={3}
      // 在 md 以上顯示，在 base 以下隱藏
      display={{ base: 'none', md: 'block' }}
    >
      <Flex
        maxW='container.xl'
        mx='auto'
        justify='space-between'
        align='center'
      >
        <Box fontWeight='bold' fontSize='xl'>
          Logo
        </Box>

        <HStack gap={4}>
          <Button variant='ghost' onClick={() => router.push('/')}>
            首頁
          </Button>
          <Button variant='ghost' onClick={() => router.push('/about')}>
            關於
          </Button>
          <ColorModeButton />
        </HStack>
      </Flex>
    </Box>
  );
};

export default Navbar;
```


---

將導覽列新增到畫面上 `src/pages/index.tsx`：
```tsx=
import Navbar from '@/components/Navbar';
import { useColorMode, useColorModeValue } from '@/components/ui/color-mode';
import { Box, Button, ClientOnly } from '@chakra-ui/react';

export default function Home() {
  const { colorMode, toggleColorMode } = useColorMode();

  const bg = useColorModeValue('white', 'gray.800');
  const color = useColorModeValue('black', 'white');

  return (
    <ClientOnly fallback={<div>Loading...</div>}>
      <Navbar />
      <Box bg={bg} color={color}>
        <Button onClick={toggleColorMode}>
          切換至 {colorMode === 'light' ? '深色' : '淺色'} 模式
        </Button>
      </Box>
    </ClientOnly>
  );
}
```
---

可再新增 about 頁面處理導覽列 `src/pages/about.tsx`：
```tsx=
import Navbar from '@/components/Navbar';
import { Box, Heading } from '@chakra-ui/react';

export default function About() {
  return (
    <>
      <Navbar />
      <Box p={8}>
        <Heading>關於頁面</Heading>
      </Box>
    </>
  );
}
```


<br/>

# 整合專案開發，完成全端網站應用

[範例專案](https://github.com/IffyArt/2025-fullstack-course-forntend/tree/feature/chakra-ui-api)