テーマとスタイリング
ダークテーマ(light / dark / auto)、密度(density)、CSS トークンによるカスタマイズ
見た目のカスタマイズは 3 段階あります: ① theme / density prop によるプリセット切替、② CSS カスタムプロパティ(--ssg-*)によるトークン上書き、③ className / classNames / cellClassName などのクラスフックです。③ はすべて string | { className, style } を受けるため、StyleX の stylex.props() の戻り値をそのまま渡せます(後述)。
デモ
テーマ(ダークモード)
<SpreadsheetGrid theme="auto" ... /> // 'light'(既定) | 'dark' | 'auto''auto'は OS / ブラウザのprefers-color-schemeに追従します。SSR ではいったん light で描画され、ハイドレーション後に追従します。- Tailwind / Mantine / HeroUI などのクラスベース dark 運用では、ページの実テーマと
prefers-color-schemeが一致しないことがあります。その場合は利用側カラースキームフックの解決値を'light' | 'dark'で渡してください。 - テーマはグリッド本体だけでなく、ポップオーバー / メニュー / パネル / ツールチップ / ドラッグゴーストにも一括適用されます。
密度(density)
<SpreadsheetGrid density="compact" ... /> // 'standard'(既定) | 'compact' | 'comfortable'行高(40 / 32 / 48px)・セル padding・アイコン寸法・文字の相対サイズが一括で切り替わります。rowHeight / headerHeight の明示指定はプリセットより優先されます。
CSS トークンによるカスタマイズ
デザイントークンは特異度ゼロ(:where(.ssg-root))で定義されているため、通常のクラス 1 つで確実に上書きできます。
.my-grid {
--ssg-accent: #7c3aed; /* アクセント(選択枠など) */
--ssg-header-bg: #f8fafc; /* ヘッダー背景 */
--ssg-invalid: #dc2626; /* バリデーション NG の色 */
--ssg-note-indicator: #f59e0b; /* セルのメモ(cellNote)の印 */
--ssg-cf-bar: rgba(52, 97, 201, 0.26); /* 条件付き書式のデータバー(--ssg-cf-seq-* / --ssg-cf-div-* / --ssg-cf-good 等も) */
--ssg-tooltip-bg: #1e293b; /* ツールチップ背景 */
}<SpreadsheetGrid className="my-grid" ... />テーマビルダー
テーマビルダーで、アクセント / ヘッダー背景 / セル背景 / 文字色 / 罫線 / 外枠 / 角丸 / 密度 / 文字サイズを実物のグリッドで動かしながら調整できます。派生色(hover / 選択 / 読み取り専用 / パネル)は自動で埋まり、生成された CSS 変数をコピーして貼るだけです。プリセット(既定 / Mantine 風 / HeroUI 風 / 業務グレー / Excel 風 / ダーク)から始められ、状態は URL に載るのでチームで共有できます。
条件付きセルスタイル
{
key: 'qty', title: '数量', width: 110,
cellClassName: ({ value }) =>
typeof value === 'number' && value < 10 ? 'my-warn-cell' : undefined,
}/* 基底クラスにチェーンして特異度で勝たせる(import 順に依存しない) */
.ssg-body-cell.my-warn-cell {
background-color: #fff7ed;
}行ホバーや readonly などの状態と組み合わせるときは、公開契約の状態クラス(.ssg-body-cell--row-hovered / .ssg-body-cell--readonly 等)と連結できます。一覧は SpreadsheetGrid props の「スタイリング用の状態クラス」を参照してください。
パーツ別スロット(classNames)と { className, style } 形
classNames はパーツごとに class を差し込むスロット表です(root / toolbar / statusBar / headerRow / headerCell / rowHeaderCell / cornerCell / bodyRow / bodyCell / groupRow / groupCell / detailBand / detailCard / iconButton / popover / menuItem / tooltip / dragGhost / checkbox / cellEditor / emptyState / filterChipBar / errorBar / scrollHint / activeCellOverlay / selectionOverlay。付与先の一覧は props リファレンス)。
classNames.* / cellClassName / getRowClassName / detailRow.className の値は GridSlotProps = string | { className?: string; style?: CSSProperties } です。文字列は従来どおり class、オブジェクト形は class に加えて style をその要素へインラインで付与します。ルート要素には style prop も使えます。
<SpreadsheetGrid
style={{ maxWidth: 960 }}
classNames={{
root: 'my-grid', // 文字列
bodyCell: { className: 'tabular', style: { fontVariantNumeric: 'tabular-nums' } },
popover: { style: { '--ssg-accent': '#7c3aed' } }, // ポータル(body 直下)にもトークンを渡せる
}}
getRowClassName={(row) => (row.archived ? { style: { opacity: 0.5 } } : undefined)}
/>- グリッドが位置決めに使う座標 / 寸法(
left/top/width/height/transform等)はグリッド側が後勝ちで上書きするため、スロットのstyleでレイアウトは壊せません。 - グリッドの高さは
styleではなくheightprop で指定します(ルートのstyle={{ height }}だけではスクロール領域の高さが決まらず、バーが切れることがあります)。詳しくは サイズ(高さ) を参照してください。 - インライン style は状態クラス(選択 / ホバー等の背景)にも勝ちます。状態で切り替えたい装飾は className 側で行ってください。
classNamesはレンダー毎に新しいオブジェクトを渡して構いません(内容の署名で memo され、行 / ヘッダーの再レンダーは増えません)。tooltip/dragGhostは命令的 DOM のため、style の数値に単位は付きません('12px'のように文字列で)。
StyleX との併用
StyleX(@stylexjs/stylex)はそのまま併用できます。reset を持たず、生成クラスは接頭辞 x なので基底 CSS と衝突しません。stylex.props(...) は { className, style } を返すため、上記のスロットへそのまま渡せます(StyleX の動的スタイルは style 側の CSS 変数で届くので、この形が必要です)。
import * as stylex from '@stylexjs/stylex';
import { vars } from './tokens.stylex'; // stylex.defineVars({ accent: '#7c3aed', danger: '#dc2626' })
const s = stylex.create({
grid: { '--ssg-accent': vars.accent, borderRadius: 8 }, // グリッドのトークンへ橋渡し
cell: { fontVariantNumeric: 'tabular-nums' },
negative: { color: vars.danger },
width: (w: number) => ({ maxWidth: w }), // 動的スタイル
});
<SpreadsheetGrid
classNames={{ root: stylex.props(s.grid), bodyCell: stylex.props(s.cell) }}
columns={[
{
key: 'amount',
title: '金額',
cellClassName: (ctx) => stylex.props(typeof ctx.value === 'number' && ctx.value < 0 && s.negative, s.width(120)),
},
]}
/>- StyleX は子孫セレクタを書けない(要素自身のクラスでしか装飾できない)ため、スロットに無い内部要素(ポップオーバー内のボタン / 入力欄等)は CSS トークン(
--ssg-*)で調整してください。 - StyleX の atomic クラスとグリッドの基底クラスは同じ特異度 (0,1,0) です。同じプロパティを指定した場合は後に読み込まれた方が勝つため、グリッド CSS を先に・StyleX の出力 CSS を後に読み込むか、
style.layer.css(グリッド全体を@layer ssg-baseに入れる)を使ってください。StyleX 側でuseLayersを有効にしている場合は、未レイヤーのグリッド CSS が常に勝ってしまうためstyle.layer.cssが必須です。 - Solid など
class属性のフレームワーク向けには StyleX にstylex.attrs()があります(将来のアダプタで同じスロット規則を使う予定です)。
Tailwind との共存(レイヤー運用、style.layer.css)はインストールを参照してください。