Design System
HyperStudy uses a design system based on CSS custom properties (design tokens) and reusable Svelte components. This ensures visual consistency across the application.
Design Tokens
Design tokens are CSS custom properties that define our visual language. They're located in frontend/src/lib/styles/design-tokens.css.
Token Categories
Colors
/* Primary colors */
--color-primary: #2196f3;
--color-primary-dark: #1976d2;
--color-primary-light: #64b5f6;
/* Status colors */
--color-success: #4caf50;
--color-warning: #ff9800;
--color-error: #f44336;
/* Neutral colors */
--color-background: #121212;
--color-surface: #1e1e1e;
--color-text: #ffffff;
--color-text-secondary: #b0b0b0;
Spacing
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-xxl: 48px;
Typography
--font-family: 'Inter', system-ui, sans-serif;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
Borders & Radius
--border-radius-sm: 4px;
--border-radius-md: 8px;
--border-radius-lg: 12px;
--border-radius-full: 9999px;
--border-width: 1px;
--border-color: #333333;
Shadows
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.3);
Using Tokens
Always use tokens instead of hardcoded values:
/* Good */
.button {
background-color: var(--color-primary);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius-md);
}
/* Avoid */
.button {
background-color: #2196f3;
padding: 8px 16px;
border-radius: 8px;
}
Z-Index Management
Z-index values are defined in frontend/src/lib/styles/z-index.css:
--z-dropdown: 100;
--z-sticky: 200;
--z-modal-backdrop: 300;
--z-modal: 400;
--z-popover: 500;
--z-tooltip: 600;
--z-toast: 700;
Use these to ensure consistent layering:
.modal {
z-index: var(--z-modal);
}
.tooltip {
z-index: var(--z-tooltip);
}
Shared Components
Reusable UI components are in frontend/src/components/shared/.
Button
<script>
import Button from '$lib/components/shared/Button.svelte';
</script>
<Button variant="primary" onclick={handleClick}>
Click Me
</Button>
<Button variant="secondary" disabled>
Disabled
</Button>
<Button variant="danger" size="small">
Delete
</Button>
Props:
variant:primary|secondary|danger|ghostsize:small|medium|largedisabled: booleantype:button|submit|reset
Modal
<script>
import Modal from '$lib/components/shared/Modal.svelte';
let showModal = $state(false);
</script>
<Modal
bind:open={showModal}
title="Confirm Action"
onclose={() => showModal = false}
>
<p>Are you sure you want to proceed?</p>
<svelte:fragment slot="footer">
<Button variant="secondary" onclick={() => showModal = false}>
Cancel
</Button>
<Button variant="primary" onclick={handleConfirm}>
Confirm
</Button>
</svelte:fragment>
</Modal>
Input
<script>
import Input from '$lib/components/shared/Input.svelte';
let value = $state('');
</script>
<Input
bind:value
label="Email"
type="email"
placeholder="Enter your email"
error={!isValid ? 'Invalid email' : ''}
/>
Props:
value: stringlabel: stringtype:text|email|password|numberplaceholder: stringerror: string (displays error message)disabled: boolean
SimpleTable
<script>
import SimpleTable from '$lib/components/shared/SimpleTable.svelte';
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'role', label: 'Role' }
];
const data = [
{ name: 'Alice', email: 'alice@example.com', role: 'Admin' },
{ name: 'Bob', email: 'bob@example.com', role: 'Member' }
];
</script>
<SimpleTable {columns} {data} />
Icon
<script>
import Icon from '$lib/components/shared/Icon.svelte';
</script>
<Icon name="check" size={24} color="var(--color-success)" />
<Icon name="warning" size={16} />
<Icon name="close" />
Available Icons:
- Navigation:
arrow-left,arrow-right,chevron-down,chevron-up - Actions:
check,close,edit,delete,add,search - Status:
warning,error,info,success - UI:
menu,settings,user,help
Creating New Components
Component Template
<script>
/**
* ComponentName - Brief description
* @component
*/
/** @type {string} */
export let variant = 'default';
/** @type {boolean} */
export let disabled = false;
</script>
<div
class="component"
class:component--disabled={disabled}
class:component--{variant}
>
<slot />
</div>
<style>
.component {
/* Use design tokens */
padding: var(--spacing-md);
background: var(--color-surface);
border-radius: var(--border-radius-md);
}
.component--disabled {
opacity: 0.5;
pointer-events: none;
}
.component--primary {
background: var(--color-primary);
}
</style>
Guidelines
- Use design tokens - Never hardcode colors, spacing, or typography
- Support variants - Provide props for common variations
- Accessibility - Include ARIA attributes and keyboard support
- Documentation - Add JSDoc comments for props
- Slots - Use slots for flexible content
- BEM naming - Use Block__Element--Modifier pattern for classes
Storybook Integration
Design tokens are automatically documented in Storybook:
- Design Tokens addon displays all tokens in a panel
- Stories showcase component variants
- Controls let users experiment with props
See Storybook documentation for details.
Migration Guide
When updating existing components to use the design system:
Before
<style>
.card {
background: #1e1e1e;
padding: 16px;
border-radius: 8px;
color: white;
}
</style>
After
<style>
.card {
background: var(--color-surface);
padding: var(--spacing-md);
border-radius: var(--border-radius-md);
color: var(--color-text);
}
</style>
Best Practices
- Consistent spacing - Use spacing tokens for all margins and padding
- Color semantics - Use semantic color names (success, error) not literal (green, red)
- Responsive tokens - Consider breakpoint-specific token values
- Dark mode ready - Tokens make theme switching straightforward
- Component composition - Build complex UIs from simple shared components