Design Tokens
Stablev0.3.0Qwik UIのデザインシステムを構成するカラー、スペーシング、シャドウなどの基本要素。CSS変数として定義されており、テーマ切り替えに対応しています。
Surface
Content
Accent
Semantic
Caution / Red
Alert / Orange
Success / Green
Link / Blue
TypeScript で使用する
Qwikコンポーネントで Design Tokens を活用する実装例
コンポーネントでの使用例
TSXButton.tsx43 lines
12345678910111213141516171819202122232425262728293031323334353637383940414243import { component$ } from '@builder.io/qwik';
import type { ButtonHTMLAttributes } from '@builder.io/qwik';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
fullWidth?: boolean;
}
export const Button = component$<ButtonProps>((props) => {
const {
variant = 'primary',
size = 'md',
fullWidth = false,
class: className,
...rest
} = props;
const baseStyles = 'inline-flex items-center justify-center font-medium rounded-lg transition-all';
const variantStyles = {
primary: 'bg-accent text-white hover:bg-accent-hover active:bg-accent-pressed',
secondary: 'bg-surface-elevated text-content border border-border hover:bg-surface-hover',
danger: 'bg-caution-red text-white hover:opacity-90'
};
const sizeStyles = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg'
};
return (
<button
{...rest}
class={`${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${
fullWidth ? 'w-full' : ''
} ${className || ''}`}
>
<Slot />
</button>
);
});カスタムフックでの使用
TypeScriptuseTheme.ts53 lines
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import { useSignal, useVisibleTask$ } from '@builder.io/qwik';
export const useTheme = () => {
const theme = useSignal<'light' | 'dark'>('light');
const systemPreference = useSignal<'light' | 'dark'>('light');
useVisibleTask$(() => {
// システムのテーマ設定を検出
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
systemPreference.value = mediaQuery.matches ? 'dark' : 'light';
// ローカルストレージから保存されたテーマを取得
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null;
if (savedTheme) {
theme.value = savedTheme;
} else {
theme.value = systemPreference.value;
}
// システムテーマの変更を監視
const handleChange = (e: MediaQueryListEvent) => {
systemPreference.value = e.matches ? 'dark' : 'light';
if (!localStorage.getItem('theme')) {
theme.value = systemPreference.value;
}
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
});
const setTheme = (newTheme: 'light' | 'dark' | 'system') => {
if (newTheme === 'system') {
localStorage.removeItem('theme');
theme.value = systemPreference.value;
} else {
localStorage.setItem('theme', newTheme);
theme.value = newTheme;
}
// ルート要素にdata-theme属性を設定
document.documentElement.dataset.theme = theme.value;
};
return {
theme: theme.value,
setTheme,
systemPreference: systemPreference.value
};
};Tailwind CSS 設定
Design Tokens を Tailwind CSS で使用するための設定方法
tailwind.config.js の設定
JavaScripttailwind.config.js110 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@aid-on/design-system/**/*.js"
],
theme: {
extend: {
colors: {
// Surface colors
'surface': 'var(--surface)',
'surface-elevated': 'var(--surface-elevated)',
'surface-hover': 'var(--surface-hover)',
'surface-pressed': 'var(--surface-pressed)',
// Content colors
'content': 'var(--content)',
'content-secondary': 'var(--content-secondary)',
'content-tertiary': 'var(--content-tertiary)',
'content-disabled': 'var(--content-disabled)',
// Accent colors
'accent': 'var(--accent)',
'accent-hover': 'var(--accent-hover)',
'accent-pressed': 'var(--accent-pressed)',
'accent-muted': 'var(--accent-muted)',
// Semantic colors
'caution-red': 'var(--caution-red)',
'alert-orange': 'var(--alert-orange)',
'success-green': 'var(--success-green)',
'link-blue': 'var(--link-blue)',
// UI colors
'border': 'var(--border)',
'divider': 'var(--divider)',
'overlay': 'var(--overlay)'
},
spacing: {
'xs': 'var(--spacing-xs)',
'sm': 'var(--spacing-sm)',
'md': 'var(--spacing-md)',
'lg': 'var(--spacing-lg)',
'xl': 'var(--spacing-xl)',
'2xl': 'var(--spacing-2xl)'
},
borderRadius: {
'sm': 'var(--radius-sm)',
'md': 'var(--radius-md)',
'lg': 'var(--radius-lg)',
'xl': 'var(--radius-xl)'
},
boxShadow: {
'sm': 'var(--shadow-sm)',
'md': 'var(--shadow-md)',
'lg': 'var(--shadow-lg)',
'xl': 'var(--shadow-xl)'
},
fontFamily: {
'sans': ['Inter', 'system-ui', 'sans-serif'],
'mono': ['Cascadia Code', 'Fira Code', 'monospace']
}
}
},
plugins: [
// カスタムプラグインでCSS変数を定義
function({ addBase }) {
addBase({
':root': {
'--surface': '#ffffff',
'--surface-elevated': '#f9fafb',
'--surface-hover': '#f3f4f6',
'--surface-pressed': '#e5e7eb',
'--content': '#111827',
'--content-secondary': '#4b5563',
'--content-tertiary': '#9ca3af',
'--content-disabled': '#d1d5db',
'--accent': '#6366f1',
'--accent-hover': '#4f46e5',
'--accent-pressed': '#4338ca',
'--accent-muted': '#e0e7ff',
'--caution-red': '#ef4444',
'--alert-orange': '#f97316',
'--success-green': '#10b981',
'--link-blue': '#3b82f6',
'--border': '#e5e7eb',
'--divider': '#f3f4f6',
'--overlay': 'rgba(0, 0, 0, 0.5)'
},
'[data-theme="dark"]': {
'--surface': '#1f2937',
'--surface-elevated': '#374151',
'--surface-hover': '#4b5563',
'--surface-pressed': '#6b7280',
'--content': '#f9fafb',
'--content-secondary': '#e5e7eb',
'--content-tertiary': '#9ca3af',
'--content-disabled': '#6b7280',
'--accent': '#818cf8',
'--accent-hover': '#a5b4fc',
'--accent-pressed': '#6366f1',
'--accent-muted': '#312e81',
'--border': '#374151',
'--divider': '#4b5563',
'--overlay': 'rgba(0, 0, 0, 0.7)'
}
});
}
]
};CSS での直接使用
CSSglobal.css74 lines
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374@import '@aid-on/design-system/dist/tokens.css';
/* カスタムコンポーネントのスタイル */
.custom-card {
background-color: var(--surface-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow-md);
}
.custom-card:hover {
background-color: var(--surface-hover);
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
transition: all 0.2s ease;
}
.custom-card-title {
color: var(--content);
font-size: 1.25rem;
font-weight: 600;
margin-bottom: var(--spacing-sm);
}
.custom-card-description {
color: var(--content-secondary);
font-size: 0.875rem;
line-height: 1.5;
}
/* ダークモード対応 */
[data-theme="dark"] .custom-card {
background-color: var(--surface);
border-color: var(--border);
}
[data-theme="dark"] .custom-card:hover {
background-color: var(--surface-elevated);
}
/* アニメーション付きボタン */
.animated-button {
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
color: white;
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius-md);
border: none;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.animated-button::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.2);
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.animated-button:hover::before {
transform: translateX(0);
}
.animated-button:active {
transform: scale(0.98);
}