はじめに
Figmaで作ったデザインと、実際にReactで実装されたコンポーネントがいつの間にかズレている——そんな経験はないでしょうか。デザイナーがコンポーネントを更新しても実装側に反映されず、逆にエンジニアが独自にスタイルを調整してFigmaと乖離していく。デザインシステムを運用したことがあるチームなら、一度は突き当たる問題だと思います。
Reshapedは、この「デザインとコードの二重管理」を根本から解消しようとするReact向けUIコンポーネントライブラリです。FigmaライブラリとReactコンポーネントが同じ設計を共有しており、デザイナーとエンジニアが同じ語彙でコンポーネントを扱えるようになっています。
とはいえ、読むより触った方が早いと思います。フォーム入力の検証がその場で反映されるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Reshapedとは
Reshapedは、"professionally crafted React & Figma components for building beautiful products"を謳うデザインシステムライブラリです。単なるコンポーネント集ではなく、Figmaのデザインファイルとコードベースが同じコンポーネント定義を参照する仕組みを軸にしています。
主な特徴
- Design = Code - FigmaコンポーネントとReactコンポーネントが同じ構造を持ち、デザイン変更とコード変更を同期させやすい
- アクセシビリティ標準対応 - WAI-ARIAに準拠したコンポーネントで、スクリーンリーダーにも配慮した実装がされている
- セマンティックなデザイントークン - テーマレイヤーを通じてダークモードを含む配色を一元管理できる
- レスポンシブなprops - ブレークポイントごとに値を切り替えられるprop設計で、メディアクエリを書かずにレイアウトを調整できる
- 国際化・RTL対応 - 右から左に読む言語を含む多言語UIに対応している
インストール
npmでインストールします。
npm install reshaped
Reshapedは使用したコンポーネントだけをバンドルに含める設計になっており、PostCSSと組み合わせて未使用スタイルを取り除けます。プロジェクトルートにpostcss.config.jsを作成してください。
const { config } = require("reshaped/config/postcss");
module.exports = config;
Reshapedのサンプルを動かす
ReshapedのTextFieldコンポーネントを使った、入力検証フォームです。hasErrorpropに検証結果を渡すだけで、赤枠とエラーメッセージの表示を切り替えられます。値を書き換えると、その場でOKとNGの表示が入れ替わります。
要点だけを抜き出すと、このような形です。
import { Reshaped, TextField, Text } from "reshaped";
function App() {
const [email, setEmail] = useState("");
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
return (
<Reshaped theme="slate">
<TextField
name="email"
value={email}
onChange={({ value }) => setEmail(value)}
hasError={email.length > 0 && !isValid}
placeholder="example@gmail.com"
/>
{isValid && <Text color="positive">有効なメールアドレスです</Text>}
</Reshaped>
);
}
実際に動かして確認できるのが下のサンプルです。メールアドレスを空にしたり、@を抜いたりして、hasErrorの表示がどう変わるか試してみてください。
TextFieldのonChangeは{ value }を受け取る独自のシグネチャになっている点に注意してください。素のReactのonChange={e => ...}とは形が違うので、最初は戸惑うかもしれません。hasErrorをtrueにするだけで見た目のエラー状態が切り替わるため、バリデーションロジックとスタイリングを分離しやすいのが分かると思います。
基本的な使い方
Reshapedのコンポーネントは、必ずReshapedプロバイダーの内側で使う必要があります。テーマの適用やカラーモードの管理はこのプロバイダーが担っています。
import { Button, Container, Reshaped } from "reshaped";
import "reshaped/themes/slate/theme.css";
const App = () => {
return (
<Reshaped theme="slate">
<Container width="652px">
<Button href="/">Get started</Button>
</Container>
</Reshaped>
);
};
themeにはあらかじめ用意されたテーマ名(例ではslate)を指定します。CSSは使うテーマごとに個別ファイルとして提供されているため、必要なテーマだけを読み込めば済みます。
実践的なユースケース
モーダルとuseToggleの組み合わせ
確認ダイアログや詳細表示など、開閉状態を持つUIはReshapedのModalとuseToggleフックで簡潔に書けます。activepropで表示状態を制御し、onCloseで外側クリックやEscキーでの閉じる操作を受け取ります。
import { Modal, Button, Text, useToggle } from "reshaped";
function Confirm() {
const { active, activate, deactivate } = useToggle(false);
return (
<>
<Button onClick={activate}>削除する</Button>
<Modal active={active} onClose={deactivate}>
<Text>本当に削除しますか?</Text>
<Button color="critical" onClick={deactivate}>
削除して閉じる
</Button>
</Modal>
</>
);
}
実際に動かせるサンプルです。ボタンを押すとモーダルが開き、外側のクリックやEscキーでも閉じられることを確認できます。
useToggleはactive・activate・deactivateをまとめて返すユーティリティフックで、開閉状態のためだけにuseStateを書く手間を省いてくれます。Modalのpositionpropを"bottom"に変えると、中央表示ではなく画面下からせり上がる形に変わります。
useThemeによるダークモード切り替え
Reshapedはセマンティックなデザイントークンを採用しているため、useThemeフックのinvertColorModeを呼ぶだけで、コンポーネント全体の配色をライト/ダークで切り替えられます。個別のコンポーネントにdark:のようなクラスを書き足す必要がありません。
import { useTheme, Button, View } from "reshaped";
function ThemeSwitch() {
const { colorMode, invertColorMode } = useTheme();
return (
<View direction="row" align="center" gap={3}>
<View.Item>Mode: {colorMode}</View.Item>
<Button onClick={invertColorMode}>モード切り替え</Button>
</View>
);
}
実際に動かして、ボタンを押したときにカード全体の背景色・文字色が切り替わる様子を確認できます。
このサンプルではCardとTextの配色を一切書き換えていませんが、invertColorModeを呼ぶだけで全体のコントラストが反転します。セマンティックトークン(neutral-fadedのような役割ベースの色指定)を使っているコンポーネントは、テーマ側の変更だけで自動的に追従する仕組みです。
まとめ
Reshapedは、FigmaとReactのコンポーネントを同じ設計で繋ぐことで、デザインシステム運用でありがちな「デザインと実装のズレ」を減らすことを狙ったライブラリです。TextFieldのようなフォームコンポーネントから、ModalとuseToggleによる状態管理、useThemeによるカラーモード切り替えまで、実務でよく使う機能が一通り揃っています。
デザイナーとの協業が多いプロジェクトや、ダークモード対応を後回しにしたくない新規プロダクトでは、一度試してみる価値があるはずです。まずは今回のサンプルを書き換えながら、ViewやCardなど他のレイアウトコンポーネントも触ってみてください。
