SolidJS でグローバルスタイルを管理する方法

現代のWebアプリケーション開発において、一貫性のあるデザインシステムを構築することは不可欠です。SolidJSを使用する際も、グローバルスタイルの適切な管理が美しく保守性の高いアプリケーションを作る鍵となります。
本記事では、SolidJSでグローバルスタイルを効率的に管理する方法について、実践的なアプローチを詳しく解説いたします。初心者の方でも理解しやすいよう、具体的なコード例とともにお伝えしていきますね。
背景
SolidJSのスタイル管理の特徴
SolidJSは、ReactやVueとは異なる独自のリアクティブシステムを持つフロントエンドフレームワークです。この特徴により、スタイル管理においても独特のアプローチが必要になります。
SolidJSの最大の特徴は、細粒度リアクティブシステムによる高いパフォーマンスです。しかし、このシステムはスタイル管理にも影響を与えます。
mermaidflowchart TD
component[SolidJSコンポーネント] -->|リアクティブ更新| styles[スタイル更新]
styles -->|DOM直接操作| dom[DOM要素]
component -->|状態変化| reactive[リアクティブシステム]
reactive -->|最小限の更新| dom
図で理解できる要点:
- SolidJSは状態変化時に最小限のDOM更新のみを実行
- スタイル更新も効率的に行われる仕組み
コンポーネントベースでのスタイル課題
コンポーネントベースの開発では、各コンポーネントが独立したスタイルを持つことが一般的です。しかし、これにはいくつかの課題があります。
課題 | 説明 | 影響 |
---|---|---|
スタイルの重複 | 同様のスタイルが複数のコンポーネントで定義される | バンドルサイズの増加 |
一貫性の欠如 | デザインシステムが統一されない | ユーザー体験の低下 |
保守性の悪化 | スタイル変更時の影響範囲が不明確 | 開発効率の低下 |
これらの課題を解決するために、グローバルスタイルの適切な管理が重要になります。
グローバルスタイルが必要なケース
グローバルスタイルが特に重要となるケースをご紹介します。
テーマカラーの管理 ブランドカラーやアクセントカラーなど、アプリケーション全体で統一したい色彩設定
タイポグラフィの統一 フォントファミリー、サイズ、行間などの文字体系の一元管理
レイアウトの基本設定 マージン、パディング、ブレークポイントなどの基準値
課題
スタイルの競合問題
SolidJSでグローバルスタイルを管理する際に最も頻繁に発生する問題がスタイルの競合です。
javascript// 問題のあるコード例
// ComponentA.tsx
const ComponentA = () => {
return (
<div class="container">
<h1 class="title">タイトルA</h1>
</div>
);
};
javascript// ComponentB.tsx
const ComponentB = () => {
return (
<div class="container">
<h1 class="title">タイトルB</h1>
</div>
);
};
上記のコードでは、両方のコンポーネントが同じクラス名を使用しているため、CSSの詳細度によってはスタイルが意図しない形で適用される可能性があります。
パフォーマンスへの影響
適切でないグローバルスタイル管理は、アプリケーションのパフォーマンスに深刻な影響を与える可能性があります。
mermaidsequenceDiagram
participant App as アプリケーション
participant CSS as CSSエンジン
participant DOM as DOM
App->>CSS: 大量のグローバルスタイル読み込み
CSS->>CSS: スタイル解析・競合解決
CSS->>DOM: スタイル適用
Note right of DOM: 初期描画の遅延
App->>CSS: 動的スタイル変更
CSS->>CSS: 再計算処理
CSS->>DOM: 再描画
Note right of DOM: ランタイムパフォーマンス低下
パフォーマンス影響の要点:
- 大量のグローバルスタイルは初期ロード時間を増加させる
- 不適切な管理は頻繁な再計算を引き起こす
保守性の課題
グローバルスタイルの保守性に関する課題は、プロジェクトの規模が大きくなるほど深刻になります。
依存関係の複雑化 グローバルスタイルを変更した際の影響範囲が把握しにくくなることです。
命名規則の統一困難 チーム開発において、一貫した命名規則を維持することが困難になります。
デッドコードの蓄積 使用されなくなったスタイルが削除されず、コードベースに残り続ける問題があります。
解決策
CSS-in-JSライブラリの活用
SolidJSでは、様々なCSS-in-JSライブラリを活用してグローバルスタイルを管理できます。代表的なライブラリとその特徴をご紹介します。
Solid Styled Components
Solid Styled Componentsは、SolidJS専用に設計されたCSS-in-JSライブラリです。
bashyarn add solid-styled-components
基本的な使用方法をご紹介します。
javascript// theme.ts - グローバルテーマの定義
export const theme = {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
background: '#ffffff',
text: '#1f2937'
},
fonts: {
body: '"Inter", sans-serif',
heading: '"Poppins", sans-serif'
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem'
}
};
テーマプロバイダーでアプリケーション全体を包みます。
javascript// App.tsx
import { ThemeProvider } from 'solid-styled-components';
import { theme } from './theme';
function App() {
return (
<ThemeProvider theme={theme}>
<MainContent />
</ThemeProvider>
);
}
グローバルスタイルの設定は以下のように行います。
javascript// GlobalStyles.tsx
import { createGlobalStyles } from 'solid-styled-components';
const GlobalStyles = createGlobalStyles`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: ${props => props.theme.fonts.body};
color: ${props => props.theme.colors.text};
background-color: ${props => props.theme.colors.background};
line-height: 1.6;
}
h1, h2, h3, h4, h5, h6 {
font-family: ${props => props.theme.fonts.heading};
margin-bottom: ${props => props.theme.spacing.md};
}
`;
Emotion CSS
EmotionはReactでも広く使用されているCSS-in-JSライブラリで、SolidJSでも活用できます。
bashyarn add @emotion/css
Emotionを使用したグローバルスタイルの設定方法です。
javascript// globalStyles.ts
import { css, Global } from '@emotion/css';
export const globalStyles = css`
:root {
--color-primary: #3b82f6;
--color-secondary: #64748b;
--color-background: #ffffff;
--color-text: #1f2937;
--font-body: "Inter", sans-serif;
--font-heading: "Poppins", sans-serif;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-body);
color: var(--color-text);
background-color: var(--color-background);
line-height: 1.6;
}
`;
アプリケーションでグローバルスタイルを適用します。
javascript// App.tsx
import { injectGlobal } from '@emotion/css';
import { globalStyles } from './globalStyles';
// グローバルスタイルの注入
injectGlobal(globalStyles);
function App() {
return (
<div>
{/* アプリケーションのコンテンツ */}
</div>
);
}
グローバルCSS変数の利用
CSS変数(カスタムプロパティ)を活用することで、JavaScriptを使わずにグローバルスタイルを管理できます。この方法はパフォーマンス面で優れています。
CSS変数の定義
まず、ルート要素にCSS変数を定義します。
css/* styles/globals.css */
:root {
/* カラーパレット */
--color-primary-50: #eff6ff;
--color-primary-500: #3b82f6;
--color-primary-900: #1e3a8a;
--color-gray-50: #f9fafb;
--color-gray-500: #6b7280;
--color-gray-900: #111827;
/* タイポグラフィ */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
/* スペーシング */
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-6: 1.5rem;
--spacing-8: 2rem;
/* ブレークポイント(メディアクエリ用) */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
}
基本的なリセットスタイルも同時に定義します。
css/* 基本リセット */
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--color-gray-900);
background-color: var(--color-gray-50);
}
/* 見出し要素の統一 */
h1, h2, h3, h4, h5, h6 {
margin: 0 0 var(--spacing-4) 0;
font-weight: var(--font-weight-bold);
line-height: 1.2;
}
h1 { font-size: 2.25rem; }
h2 { font-size: 1.875rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
h5 { font-size: var(--font-size-lg); }
h6 { font-size: var(--font-size-base); }
SolidJSでのCSS変数活用
SolidJSコンポーネントでCSS変数を活用する方法をご紹介します。
javascript// components/Button.tsx
import { JSX } from 'solid-js';
import './Button.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: JSX.Element;
onClick?: () => void;
}
const Button = (props: ButtonProps) => {
const variant = () => props.variant || 'primary';
const size = () => props.size || 'md';
return (
<button
class={`btn btn--${variant()} btn--${size()}`}
onClick={props.onClick}
>
{props.children}
</button>
);
};
export default Button;
対応するCSSファイルでCSS変数を使用します。
css/* components/Button.css */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 0.375rem;
font-weight: var(--font-weight-medium);
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
/* サイズバリエーション */
.btn--sm {
padding: var(--spacing-1) var(--spacing-2);
font-size: var(--font-size-sm);
}
.btn--md {
padding: var(--spacing-2) var(--spacing-4);
font-size: var(--font-size-base);
}
.btn--lg {
padding: var(--spacing-4) var(--spacing-6);
font-size: var(--font-size-lg);
}
/* カラーバリエーション */
.btn--primary {
background-color: var(--color-primary-500);
color: white;
}
.btn--primary:hover {
background-color: var(--color-primary-900);
}
.btn--secondary {
background-color: var(--color-gray-500);
color: white;
}
.btn--secondary:hover {
background-color: var(--color-gray-900);
}
SolidJSのStyled Componentsアプローチ
SolidJS独自のアプローチとして、solid-styled-componentsを使ったより高度なスタイル管理方法をご紹介します。
テーマシステムの構築
より複雑なテーマシステムを構築してみましょう。
javascript// theme/types.ts
export interface Theme {
colors: {
primary: ColorScale;
secondary: ColorScale;
gray: ColorScale;
semantic: SemanticColors;
};
typography: Typography;
spacing: Spacing;
breakpoints: Breakpoints;
shadows: Shadows;
}
interface ColorScale {
50: string;
100: string;
500: string;
900: string;
}
interface SemanticColors {
success: string;
warning: string;
error: string;
info: string;
}
interface Typography {
fontFamily: {
body: string;
heading: string;
mono: string;
};
fontSize: Record<string, string>;
fontWeight: Record<string, number>;
}
具体的なテーマオブジェクトを定義します。
javascript// theme/default.ts
import { Theme } from './types';
export const defaultTheme: Theme = {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
900: '#1e3a8a'
},
secondary: {
50: '#f8fafc',
100: '#f1f5f9',
500: '#64748b',
900: '#0f172a'
},
gray: {
50: '#f9fafb',
100: '#f3f4f6',
500: '#6b7280',
900: '#111827'
},
semantic: {
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
info: '#3b82f6'
}
},
typography: {
fontFamily: {
body: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif',
heading: '"Poppins", sans-serif',
mono: '"Fira Code", "Monaco", monospace'
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem'
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700
}
},
spacing: {
1: '0.25rem',
2: '0.5rem',
4: '1rem',
6: '1.5rem',
8: '2rem',
12: '3rem',
16: '4rem'
},
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px'
},
shadows: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)'
}
};
具体例
基本的なグローバルスタイルの設定
実際のSolidJSアプリケーションでグローバルスタイルを設定する完全な例をご紹介します。
まず、プロジェクトの構造を整理しましょう。
bashyarn create solid my-app
cd my-app
yarn add solid-styled-components @emotion/css
アプリケーションのエントリーポイントでグローバルスタイルを設定します。
javascript// src/App.tsx
import { Component } from 'solid-js';
import { ThemeProvider } from 'solid-styled-components';
import { GlobalStyles } from './styles/GlobalStyles';
import { defaultTheme } from './theme/default';
import { HomePage } from './pages/HomePage';
const App: Component = () => {
return (
<ThemeProvider theme={defaultTheme}>
<GlobalStyles />
<HomePage />
</ThemeProvider>
);
};
export default App;
グローバルスタイルコンポーネントを作成します。
javascript// src/styles/GlobalStyles.tsx
import { createGlobalStyles } from 'solid-styled-components';
export const GlobalStyles = createGlobalStyles`
/* リセットCSS */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
scroll-behavior: smooth;
}
body {
font-family: ${props => props.theme.typography.fontFamily.body};
font-size: ${props => props.theme.typography.fontSize.base};
font-weight: ${props => props.theme.typography.fontWeight.normal};
line-height: 1.6;
color: ${props => props.theme.colors.gray[900]};
background-color: ${props => props.theme.colors.gray[50]};
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 見出し要素 */
h1, h2, h3, h4, h5, h6 {
font-family: ${props => props.theme.typography.fontFamily.heading};
font-weight: ${props => props.theme.typography.fontWeight.bold};
line-height: 1.2;
margin-bottom: ${props => props.theme.spacing[4]};
}
h1 { font-size: ${props => props.theme.typography.fontSize['3xl']}; }
h2 { font-size: ${props => props.theme.typography.fontSize['2xl']}; }
h3 { font-size: ${props => props.theme.typography.fontSize.xl}; }
h4 { font-size: ${props => props.theme.typography.fontSize.lg}; }
/* リンク要素 */
a {
color: ${props => props.theme.colors.primary[500]};
text-decoration: none;
transition: color 0.2s ease-in-out;
}
a:hover {
color: ${props => props.theme.colors.primary[900]};
text-decoration: underline;
}
/* フォーカス状態 */
*:focus {
outline: 2px solid ${props => props.theme.colors.primary[500]};
outline-offset: 2px;
}
/* スクロールバー(Webkit) */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: ${props => props.theme.colors.gray[100]};
}
::-webkit-scrollbar-thumb {
background: ${props => props.theme.colors.gray[500]};
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: ${props => props.theme.colors.gray[900]};
}
`;
テーマ管理の実装
動的なテーマ切り替え機能を実装してみましょう。
javascript// src/context/ThemeContext.tsx
import { createContext, useContext, ParentComponent } from 'solid-js';
import { createSignal } from 'solid-js';
import { defaultTheme } from '../theme/default';
import { darkTheme } from '../theme/dark';
type ThemeMode = 'light' | 'dark';
interface ThemeContextValue {
theme: () => any;
themeMode: () => ThemeMode;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue>();
export const ThemeProvider: ParentComponent = (props) => {
const [themeMode, setThemeMode] = createSignal<ThemeMode>('light');
const theme = () => themeMode() === 'light' ? defaultTheme : darkTheme;
const toggleTheme = () => {
setThemeMode(prev => prev === 'light' ? 'dark' : 'light');
// ローカルストレージに保存
localStorage.setItem('theme-mode', themeMode());
};
// 初期化時にローカルストレージから読み込み
const savedTheme = localStorage.getItem('theme-mode') as ThemeMode;
if (savedTheme) {
setThemeMode(savedTheme);
}
const value = {
theme,
themeMode,
toggleTheme
};
return (
<ThemeContext.Provider value={value}>
{props.children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
ダークテーマの定義を追加します。
javascript// src/theme/dark.ts
import { Theme } from './types';
export const darkTheme: Theme = {
colors: {
primary: {
50: '#1e3a8a',
100: '#3b82f6',
500: '#60a5fa',
900: '#93c5fd'
},
secondary: {
50: '#0f172a',
100: '#334155',
500: '#94a3b8',
900: '#f1f5f9'
},
gray: {
50: '#111827',
100: '#1f2937',
500: '#9ca3af',
900: '#f9fafb'
},
semantic: {
success: '#34d399',
warning: '#fbbf24',
error: '#f87171',
info: '#60a5fa'
}
},
// typography, spacing, breakpoints, shadowsは
// defaultThemeと同じ値を使用
typography: {
// ...defaultThemeと同じ
},
spacing: {
// ...defaultThemeと同じ
},
breakpoints: {
// ...defaultThemeと同じ
},
shadows: {
// ダークテーマ用に調整
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.3)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.3)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.3)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.3)'
}
};
テーマトグルボタンコンポーネントを作成します。
javascript// src/components/ThemeToggle.tsx
import { Component } from 'solid-js';
import { styled } from 'solid-styled-components';
import { useTheme } from '../context/ThemeContext';
const ToggleButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border: none;
border-radius: 50%;
background-color: ${props => props.theme.colors.primary[500]};
color: white;
cursor: pointer;
transition: all 0.2s ease-in-out;
box-shadow: ${props => props.theme.shadows.md};
&:hover {
background-color: ${props => props.theme.colors.primary[900]};
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
`;
export const ThemeToggle: Component = () => {
const { themeMode, toggleTheme } = useTheme();
return (
<ToggleButton onClick={toggleTheme} title="テーマを切り替え">
{themeMode() === 'light' ? '🌙' : '☀️'}
</ToggleButton>
);
};
レスポンシブ対応
レスポンシブデザインに対応したグローバルスタイルの実装方法をご紹介します。
javascript// src/styles/responsive.ts
import { css } from 'solid-styled-components';
// ブレークポイントヘルパー関数
export const mediaQuery = {
sm: (styles: string) => css`
@media (min-width: 640px) {
${styles}
}
`,
md: (styles: string) => css`
@media (min-width: 768px) {
${styles}
}
`,
lg: (styles: string) => css`
@media (min-width: 1024px) {
${styles}
}
`,
xl: (styles: string) => css`
@media (min-width: 1280px) {
${styles}
}
`
};
// レスポンシブユーティリティ
export const responsive = {
container: css`
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 0 1rem;
${mediaQuery.sm(`
padding: 0 1.5rem;
`)}
${mediaQuery.md(`
padding: 0 2rem;
`)}
${mediaQuery.lg(`
padding: 0 2.5rem;
`)}
`,
grid: css`
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
${mediaQuery.sm(`
grid-template-columns: repeat(2, 1fr);
`)}
${mediaQuery.md(`
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
`)}
${mediaQuery.lg(`
grid-template-columns: repeat(4, 1fr);
gap: 2rem;
`)}
`
};
レスポンシブコンテナコンポーネントの作成例です。
javascript// src/components/Container.tsx
import { Component, JSX } from 'solid-js';
import { styled } from 'solid-styled-components';
interface ContainerProps {
children: JSX.Element;
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
}
const StyledContainer = styled.div<{ size: string }>`
width: 100%;
margin: 0 auto;
padding: 0 ${props => props.theme.spacing[4]};
${props => {
const maxWidths = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
full: '100%'
};
return `max-width: ${maxWidths[props.size as keyof typeof maxWidths]};`;
}}
@media (min-width: ${props => props.theme.breakpoints.sm}) {
padding: 0 ${props => props.theme.spacing[6]};
}
@media (min-width: ${props => props.theme.breakpoints.md}) {
padding: 0 ${props => props.theme.spacing[8]};
}
`;
export const Container: Component<ContainerProps> = (props) => {
return (
<StyledContainer size={props.size || 'lg'}>
{props.children}
</StyledContainer>
);
};
まとめ
最適なアプローチの選択指針
SolidJSでグローバルスタイルを管理する際の選択指針をまとめます。
アプローチ | 適用ケース | メリット | デメリット |
---|---|---|---|
CSS-in-JS | 動的テーマ、複雑な状態管理 | 型安全性、動的スタイル | バンドルサイズ増加 |
CSS変数 | シンプルなテーマ、パフォーマンス重視 | 高速、軽量 | 動的制御の制限 |
Styled Components | コンポーネント指向開発 | 開発体験良好 | 学習コスト |
小規模プロジェクト: CSS変数を活用したシンプルなアプローチがおすすめです。
中規模プロジェクト: Solid Styled ComponentsとCSS変数の組み合わせが効果的です。
大規模プロジェクト: 包括的なテーマシステムとCSS-in-JSライブラリの活用を検討してください。
パフォーマンス考慮事項
グローバルスタイル管理におけるパフォーマンスの最適化ポイントをご紹介します。
バンドルサイズの最適化 使用していないスタイルは自動的に除去されるようツリーシェイキングを活用しましょう。
ランタイムパフォーマンス CSS変数の活用により、JavaScriptでの計算を最小限に抑えることができます。
初期ロード時間 クリティカルCSSの優先読み込みにより、初期描画速度を向上させることが可能です。
適切なグローバルスタイル管理により、保守性が高く、パフォーマンスに優れたSolidJSアプリケーションを構築できます。プロジェクトの要件に応じて最適なアプローチを選択し、一貫性のある美しいユーザーインターフェースを実現してくださいね。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来