最近では、WebサイトやWebアプリケーション、あるいはOSにおいてダークモードがサポートされているケースが増えてきています。
(PC向けのWebアプリケーションとして)要件として必須であるケースはまだそう多くないかもしれませんが、UX向上のためにできればダークモードも提供できるようにしておきたいところです。
最近、UIフレームワークとしてCloudscapeを利用する機会があるのですが、Cloudscapeではダークモードがサポートされており簡単に導入することができます。
本エントリでは、Cloudscape( +Next.js)を利用したアプリケーションにダークモードの切り替え機能を実装する方法を紹介します。
なお、今回作成したサンプルは以下のリポジトリで公開しています。
https://github.com/amotz/cloudscape-dark-mode-sample
検証環境
- next
14.0.4
- react
18.2.0
- cloudscape-design/components
3.0.468
- cloudscape-design/global-styles
1.0.20
Cloudscapeとは
Cloudscapeは、AWSが作成しOSSとして公開しているデザインシステムです。
主にAWSの製品やサービス(マネジメントコンソール)向けに構築され使用されています。
UIフレームワークとして様々なコンポーネントが用意されており、AWSマネジメントコンソールに近い雰囲気のUIを簡単に実現できます。
なお、2023/12/22時点では、CloudscapeはReactのみをサポートしています。
Cloudscapeの導入に関しては、以下が参考になります。
Cloudscapeでのダークモード対応
比較的リッチなUIフレームワークでは、ダークモードがサポートされているケースも多いと思いますが、Cloudscapeにおいてもダークモードがサポートされています。
CloudscapeではGlobal StyleのJSヘルパーが用意されており、こちらを利用することでダークモード/ライトモードの切り替えを簡単に行うことができます。
Cloudscape - Manipulate global styles using JS helpers
import { applyMode, Mode } from '@cloudscape-design/global-styles';
// apply a color mode
applyMode(Mode.Dark);
applyMode(Mode.Light);
実装サンプル
それでは、実際にCloudscape(+ Next.js)を利用してダークモード実装を行ってみます。
今回は、Toggleコンポーネントを利用してダークモードの切り替えを行うサンプルを作成します。
適当にCloudscapeを利用したNext.jsアプリケーションの画面を作成してから、ダークモード切り替えを行う機能を仕込んでみます。
pages/index.tsx
import {
AppLayout,
BreadcrumbGroup,
SideNavigation,
Flashbar,
ContentLayout,
Header,
Container,
Link,
TopNavigation,
SpaceBetween,
FormField,
Form,
Button,
Input,
Tiles,
HelpPanel,
Toggle,
Alert,
} from "@cloudscape-design/components";
import { applyMode, Mode } from "@cloudscape-design/global-styles";
import { useState, useEffect } from "react";
export default function Home() {
const [tileValue, setTileValue] = useState("");
const [useDarkMode, setUseDarkMode] = useState(false);
useEffect(() => {
applyMode(useDarkMode ? Mode.Dark : Mode.Light);
}, [useDarkMode]);
return (
<>
<TopNavigation
identity={{
href: "#",
title: "Sample Service with Cloudscape",
}}
utilities={[
{
type: "button",
iconName: "notification",
title: "Notifications",
ariaLabel: "Notifications (unread)",
badge: true,
disableUtilityCollapse: false,
},
{
type: "menu-dropdown",
iconName: "settings",
ariaLabel: "Settings",
title: "Settings",
items: [
{
id: "settings-org",
text: "Organizational settings",
},
{
id: "settings-project",
text: "Project settings",
},
],
},
{
type: "menu-dropdown",
text: "Customer Name",
description: "email@example.com",
iconName: "user-profile",
items: [
{ id: "profile", text: "Profile" },
{ id: "preferences", text: "Preferences" },
{ id: "security", text: "Security" },
{
id: "support-group",
text: "Support",
items: [
{
id: "documentation",
text: "Documentation",
href: "#",
external: true,
externalIconAriaLabel: " (opens in new tab)",
},
{ id: "support", text: "Support" },
{
id: "feedback",
text: "Feedback",
href: "#",
external: true,
externalIconAriaLabel: " (opens in new tab)",
},
],
},
{ id: "signout", text: "Sign out" },
],
},
]}
/>
<AppLayout
breadcrumbs={
<BreadcrumbGroup
items={[
{ text: "Home", href: "#" },
{ text: "Service", href: "#" },
]}
/>
}
navigationOpen={true}
navigation={
<SideNavigation
header={{
href: "#",
text: "Sample Service",
}}
items={[{ type: "link", text: `Page #1`, href: `#` }]}
/>
}
notifications={
<Flashbar
items={[
{
type: "info",
dismissible: true,
content: "This is an info message.",
id: "message_1",
},
]}
/>
}
toolsOpen={true}
tools={<HelpPanel header={<h2>Overview</h2>}>Help content</HelpPanel>}
content={
<ContentLayout
header={
<Header
variant="h1"
info={<Link variant="info">Info</Link>}
actions={
<Toggle
onChange={({ detail }) => setUseDarkMode(detail.checked)}
checked={useDarkMode}
>
Dark Mode
</Toggle>
}
>
Page header
</Header>
}
>
<Container
header={<Header variant="h2">Form container header</Header>}
>
<Form
actions={
<SpaceBetween direction="horizontal" size="xs">
<Button formAction="none" variant="link">
Cancel
</Button>
<Button variant="primary">Submit</Button>
</SpaceBetween>
}
>
<SpaceBetween direction="vertical" size="l">
<Alert statusIconAriaLabel="Warning" type="warning">
This is a warning message.
</Alert>
<FormField label="First field">
<Input value="" />
</FormField>
<FormField label="Second field">
<Input value="" />
</FormField>
<FormField label="Third field">
<Tiles
onChange={({ detail }) => setTileValue(detail.value)}
value={tileValue}
items={[
{
label: "Item 1 label",
description: "This is a description for item 1",
value: "item1",
},
{
label: "Item 2 label",
description: "This is a description for item 2",
value: "item2",
},
]}
/>{" "}
</FormField>
</SpaceBetween>
</Form>
</Container>
</ContentLayout>
}
/>
</>
);
}
これでダークモード切り替えの機能が実装できたので、ビルドしてみます。
アプリを起動してみると、ダークモードの切り替えができるようになっています!
おわりに
Cloudscapeを利用したNext.jsアプリケーションにダークモードの切り替え機能を実装してみました。
リッチなUIフレームワークはダークモードがサポートされているケースも多いと思うので、要件として必須でなくとも実装コストが低そうであれば積極的に導入していきたいですね。
どなたかの参考になれば幸いです。