🐦

ReactとCSSの新しいUIフレームワークを作った

2023/02/16に公開
1

リンク

公式サイト

https://camome.net

デモサイト

https://saazy.camome.net

概要

Camome UI は React または HTML + CSS で使用できる UI フレームワークです。

ソースは React + CSS Modules で書かれており、 それを React コードからインポートするか、静的なクラス名で出力された CSS ファイルを読み込むことで使用できます。

特徴

軽量なバンドルサイズ

  • 例えば @mui/material@5.11.6Button は単体で16.9KBあり、それに Emotion や ThemeProvider も加わります。
  • それに対して Camome UI の Buttonは単体で 1.42KB(CSS 含む)、それにグローバル CSS の 4KB が加わります(計測方法)。

これらのフレームワークは言ってみれば「フルスタック」であり、どんなウェブサイト・アプリでも構築できる柔軟性と機能を持っています。しかしそれらすべてを必要とするサイトは多くないはずです。Camome UI はすべてをカバーするよりも軽量化を重視しています

@camome/coreCSS Modulesでスタイリングされており、NPM で配布しているパッケージ内で *.module.scssimport しています。つまり、バンドラーを経由しないと使用することができません。しかし Next.js であれば next.config.js{ transpilePackages: ["@camome/core"]} を設定するだけだし、Vite は最初からトランスパイルしてくれるので、そんなに面倒ではないと思います。

この設計の理由は、全コンポーネントの CSS を一括で読み込みたくないからです。JavaScript コードもツリーシェイキング可能なので、import したコンポーネントのコードのみがバンドルされるようにしてあります。

高い汎用性とカスタマイズ性

軽量さを重視していると言っても、実際のサイトの実装で使いにくかったら意味がありません。

Camome UI では solid, soft, outline, ghostvariant として定義してあり、各コンポーネントが実装(継承?)しています。<Button variant="soft" /> のように Prop を渡すことで切り替えられます。

Buttonのsolid, soft, outline, ghostの各バリアント

また、 colorScheme によってセマンティックな命名の色を選択できます。これはすべての variant に適用できます。

Buttonのprimary, neutral, info, warn, success, dangerの各カラースキーム

カスタムテーマとダークモード

Camome UIコンポーネントがグリッド状に配置されている。対角線でライト・ダークテーマが分かれている。

そしてこれらの色やその他のデザイントークンはすべてカスタマイズ可能です。camome.config.js をプロジェクトルートに作成し、CLI コマンドを実行することでカスタムテーマの CSS ファイルを出力できます。

// camome.config.js
import { defineConfig } from "@camome/system";
// 型補完が有効です
export default defineConfig({
  themes: {
    common: {
      color: {
        primary: {
          // カスタムのプライマリカラー
          0: "#faf5ff",
          1: "#f3e8ff",
          // ...
          // `soft` variantに使用する色を変更
          // コールバック関数を渡すことで他のトークンを参照可能
          soft: {
            bg: (get) => lighter(get("color.primary.0")),
          },
        },
      },
    },
    // `light`, `dark` のそれぞれでも設定可能
    light: {},
    dark: {},
  },
});
npx camome theme --output ./styles/custom-theme.css

デザイントークンは 以下のような CSS 変数として出力され、 @camome/core から使用されています。また、これらをアプリケーション内のスタイリングにも使用することで、デザインに一貫性を持たせられたり、上述の variant のスタイルを継承できたり、ダークテーマに自動で対応できたりといった効用が得られます。

@layer cmm.theme {
  :root[data-theme="light"] {
    --cmm-color-primary-font: var(--cmm-color-primary-7);
    --cmm-color-primary-emphasis: var(--cmm-color-primary-6);
    /* ... */
  }
}

テーマの切り替えは <html> 要素に data-theme="<light または dark>" 属性を付与することで実現できます。

<html data-theme="<light または dark>">
  <!-- ... -->
</html>

デザイントークンの中でも Semantic な色を使用することで、[data-theme="dark"] & {...} のようなダークテーマ用のスタイルを大量に書く必要がなくなります。多くの場合は以下のような CSS を書くだけで自動的に light と dark に対応できます。

.card {
  border: 1px solid var(--cmm-color-border-base);
  background: var(--cmm-color-surface-1);

  h2 {
    color: var(--cmm-color-font-base);
  }

  p {
    color: var(--cmm-color-font-subtle);
  }
}

@layer によるスタイルの確実なオーバーライド

CSS Cascade Layersをサポートしているため、詳細度を気にすることなくオーバーライドが可能です。👇 以下の宣言が最初にインポートされればあとは面倒なことを考えなくても済みます。

/* @camome/system/dist/theme.css */
@layer cmm.reset, cmm.theme, cmm.base, cmm.components;

これに関しては Bootstrap などの既存のフレームワークでも、@import(bootstrap.css) layer(bootstrap); のようにレイヤーを宣言することは可能なようです。

フォーム用ユーティリティ

input 系コンポーネントは labelerror などの props を受け取ると、自動で id を発行して label 要素や aria-describedby 属性を適切に紐付けます。Web 標準以外の挙動は行わないので、react-hook-formのようなライブラリとの相性はかなりいいと思います。

詳しくはフォームを参照してください。

// react-hook-form
const { register } = useForm();

return (
  <form>
    <TextInput
      label="Name"
      placeholder="Your name..."
      error={errors.firstName?.message}
      {...register("name", {
        required: errMsg.required,
      })}
    />
    <RadioGroup label="Job title" aria-required orientation="horizontal">
      <Radio label="Developer" value="developer" {...register("jobTitle")} />
      <Radio label="Other" value="other" {...register("jobTitle")} />
    </RadioGroup>
    <Checkbox
      label="Agree to Privacy Policy"
      {...register("privacy", {
        required: errMsg.required,
      })}
      error={errors.privacy?.message}
    />
    <Button type="submit" variant="soft">
      Submit
    </Button>
  </form>
);

アクセシブル

@camome/core ではイベントハンドラーや useEffect を使用していませんが、キーボードでの操作やその他アクセシビリティを保証できるように実装しています。Switchのようなコンポーネントも CSS と HTML だけで実装されていますが、ちゃんとフォーカスできるようにしてあります。

JavaScript が必須になるような複雑なコンポーネント(例: Menu)は、Headless UI のような外部ライブラリと接続して使うための「ヘッドオンリー」コンポーネントとして提供しています。しかし利便性のためには @camome/core/headlessui のようなディレクトリを切って公式実装を配布したほうがいいかなとも思っています。

まとめ

よければぜひ使ってみてください。

GitHub リポジトリにスターしていただけるとうれしいです 🙏

[2023-02-18] ⭐️122 ありがとうございます!

GitHubで編集を提案

Discussion

hato-codehato-code

UIがめっちゃ素敵ですね!
開発過程も知りたいです!

Hidden comment