@ybaillet/elysium
v1.0.7
Published
Logitech Software Design System
Maintainers
Readme
Elysium Component Library
A production-ready React + TypeScript component library with a Figma tokens-based design system, built for Electron desktop applications.
Features
- Dual-theme support: G-Hub theme (Cyan primary #00B6FA) and Options+ theme (Teal primary #00FDCF)
- Light/Dark mode: Automatic mode switching with localStorage persistence
- Figma tokens: Single source of truth from Figma design tokens
- Accessible: Built with Radix UI primitives for full accessibility
- TypeScript: Full type safety with exported types
- Custom Font: BrownLogitechPan font family included (12 weights)
- 330+ Icons: Custom SVG icon library
Installation
npm install @ybaillet/elysiumNote: Peer dependencies (React, Radix UI primitives, etc.) are automatically installed with npm 7+. If you're using an older npm version, you may need to install them manually.
Quick Start
All 4 steps below are required for the components to display correctly.
Step 1: Import Styles (Required)
Import the component styles in your main entry file. This includes the compiled Tailwind CSS classes used by all components:
// In your main.tsx or index.tsx (your app's entry point)
import '@ybaillet/elysium/styles';Step 2: Import Fonts (Optional but Recommended)
Import the BrownLogitechPan font family for the authentic Logitech look:
import '@ybaillet/elysium/fonts';Step 3: Wrap with ThemeProvider (Required)
The ThemeProvider is essential - it generates all CSS variables that components depend on. Without it, colors will not display correctly.
// App.tsx
import { ThemeProvider } from '@ybaillet/elysium';
function App() {
return (
<ThemeProvider defaultTheme="ghub" defaultMode="dark">
{/* Your entire app goes here */}
<YourApp />
</ThemeProvider>
);
}
export default App;Important: The ThemeProvider must wrap your entire application. It dynamically injects CSS variables (like --color-surface-primary-default, --color-text-neutral-default, etc.) that all components use for theming.
Step 4: Configure Tailwind CSS (Required if using Tailwind)
If your project uses Tailwind CSS, you must add the Elysium dist folder to your content paths. This ensures Tailwind doesn't purge the CSS classes used by Elysium components:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
// Add this line - required for Elysium components to work
'./node_modules/@ybaillet/elysium/dist/**/*.{js,cjs}',
],
// ... rest of your config
};Complete Setup Example
Here's a complete example of a properly configured app:
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// Step 1: Import Elysium styles
import '@ybaillet/elysium/styles';
// Step 2: Import fonts (optional)
import '@ybaillet/elysium/fonts';
// Your own styles (if any) should come after
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);// App.tsx
import { ThemeProvider, Button, Card, Switch } from '@ybaillet/elysium';
function App() {
return (
// Step 3: Wrap with ThemeProvider
<ThemeProvider defaultTheme="ghub" defaultMode="dark">
<div className="min-h-screen p-8">
<Card>
<Button variant="primary">Click Me</Button>
<Switch />
</Card>
</div>
</ThemeProvider>
);
}
export default App;Troubleshooting
Colors not displaying correctly?
- Ensure
ThemeProviderwraps your entire app (not just individual components) - Verify
@ybaillet/elysium/stylesis imported in your entry file - If using Tailwind, check that the Elysium content path is in your
tailwind.config.js
Components look unstyled?
- Make sure styles are imported before your own CSS files
- Check browser console for any import errors
Theme System
Elysium supports two branded themes, each with light and dark modes:
Available Themes
| Theme ID | Brand Name | Primary Color | Use Case |
|----------|------------|---------------|----------|
| ghub | G-Hub | Cyan (#00B6FA) | Gaming peripherals, high-energy interfaces |
| options+ | Options+ | Teal (#00FDCF) | Professional peripherals, productivity tools |
Note: The theme IDs match the brand names for clarity. The theme definition files are
themeGHub.tsandthemeOptionsPlus.ts.
Each theme automatically adjusts all component colors, including surfaces, text, icons, and interactive states.
Setting Up a Theme
Wrap your app with ThemeProvider and specify your theme:
import { ThemeProvider, Button, Switch, Slider } from '@ybaillet/elysium';
// G-Hub theme (Cyan) - Dark mode
function GHubApp() {
return (
<ThemeProvider defaultTheme="ghub" defaultMode="dark">
<Button variant="primary">Gaming Button</Button>
<Switch /> {/* Cyan accent when on */}
<Slider value={[50]} /> {/* Cyan track */}
</ThemeProvider>
);
}
// Options+ theme (Teal) - Light mode
function OptionsPlusApp() {
return (
<ThemeProvider defaultTheme="options+" defaultMode="light">
<Button variant="primary">Professional Button</Button>
<Switch /> {/* Teal accent when on */}
<Slider value={[50]} /> {/* Teal track */}
</ThemeProvider>
);
}Switching Themes Dynamically
Use the useTheme hook to change themes or modes at runtime:
import { useTheme, Button } from '@ybaillet/elysium';
function ThemeSwitcher() {
const { theme, mode, themeBrand, setTheme, setMode, toggleMode } = useTheme();
return (
<div>
{/* theme = 'ghub' or 'options+', themeBrand = 'G-Hub' or 'Options+' */}
<p>Current: {themeBrand} ({mode})</p>
{/* Switch between themes */}
<Button onClick={() => setTheme('ghub')}>G-Hub Theme</Button>
<Button onClick={() => setTheme('options+')}>Options+ Theme</Button>
{/* Toggle light/dark mode */}
<Button onClick={toggleMode}>Toggle Dark/Light</Button>
<Button onClick={() => setMode('dark')}>Force Dark</Button>
</div>
);
}Theme-Aware Components
All components automatically adapt to the current theme. The primary color changes based on the active theme:
import { ThemeProvider, Button, ProgressIndicator, Checkbox } from '@ybaillet/elysium';
function MyApp() {
return (
<ThemeProvider defaultTheme="ghub" defaultMode="dark">
{/* All these components use Cyan (G-Hub) accents */}
<Button variant="primary">Primary Action</Button>
<ProgressIndicator value={75} />
<Checkbox defaultChecked />
{/* Switching to options+ would make them all Teal instead */}
</ThemeProvider>
);
}Components
Core Components
| Component | Description | |-----------|-------------| | Accordion | Expandable content sections | | Button | Primary interactive element with multiple variants | | Card | Container with header, content, and footer | | Checkbox | Toggle selection control | | Dialog | Modal overlay for focused interactions | | DropdownMenu | Context menu with nested options | | IconButton | Icon-only button with aria-label | | Input | Text input with validation states | | ListItem | Interactive list row with various controls | | Pagination | Page navigation control | | ProgressIndicator | Visual progress display | | RadioGroup | Single-select option group | | Search | Search input with clear functionality | | Select | Dropdown selection control | | Slider | Range value selector | | Snackbar | Toast notifications | | Switch | Toggle control | | Tabs | Horizontal tab navigation | | TabsVertical | Vertical tab navigation | | TabViewer | Tab content manager | | Textarea | Multi-line text input | | Tooltip | Contextual information popup |
Specialized Components
| Component | Description | |-----------|-------------| | ActuationPoint | Vertical slider for actuation settings | | AssignmentLabel | Key assignment display | | Connectivity | Device connection status indicator | | RapidTrigger | Horizontal slider for rapid trigger settings |
Component Usage Examples
Many components require state management. Here are complete examples showing how to properly use them:
Controlled Components with useState
import { useState } from 'react';
import {
ActuationPoint,
RapidTrigger,
Slider,
Switch,
Checkbox,
Input,
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem
} from '@ybaillet/elysium';
function ControlledComponentsExample() {
// Slider-based components
const [actuationValue, setActuationValue] = useState(1);
const [rapidTriggerValue, setRapidTriggerValue] = useState(0.5);
const [sliderValue, setSliderValue] = useState([50]);
// Toggle components
const [switchOn, setSwitchOn] = useState(false);
const [checked, setChecked] = useState(false);
// Text input
const [text, setText] = useState('');
// Select
const [selected, setSelected] = useState('option1');
return (
<div className="space-y-4">
{/* ActuationPoint - vertical slider */}
<ActuationPoint
value={actuationValue}
onChange={setActuationValue}
/>
{/* RapidTrigger - horizontal slider */}
<RapidTrigger
value={rapidTriggerValue}
onChange={setRapidTriggerValue}
/>
{/* Standard Slider */}
<Slider
value={sliderValue}
onValueChange={setSliderValue}
min={0}
max={100}
/>
{/* Switch toggle */}
<Switch
checked={switchOn}
onCheckedChange={setSwitchOn}
/>
{/* Checkbox */}
<Checkbox
checked={checked}
onCheckedChange={setChecked}
/>
{/* Text Input */}
<Input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type here..."
/>
{/* Select dropdown */}
<Select value={selected} onValueChange={setSelected}>
<SelectTrigger>
<SelectValue placeholder="Choose..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
</div>
);
}Uncontrolled Components (No State Needed)
Some components work without explicit state management:
import { Button, Checkbox, Switch } from '@ybaillet/elysium';
function UncontrolledExample() {
return (
<div>
{/* Button with click handler */}
<Button onClick={() => console.log('Clicked!')}>
Click Me
</Button>
{/* Checkbox with default value */}
<Checkbox defaultChecked />
{/* Switch with default value */}
<Switch defaultChecked />
</div>
);
}Pattern Components
| Component | Description | |-----------|-------------| | Navbar | Application header with navigation | | DeviceNav | Device sidebar navigation | | Sensitivity | DPI/Sensitivity control panel |
Icons
Access 330+ custom icons:
import { Icons } from '@ybaillet/elysium';
// Use any icon
<Icons.Settings className="w-6 h-6" />
<Icons.BatteryFull className="w-4 h-4 text-green-500" />
<Icons.Bluetooth className="w-5 h-5" />Tokens
The design system uses a 2-layer token architecture:
Raw Colors (colorRaw)
Base color palette with literal hex values stored in src/tokens/colors/raw.ts. These are the foundational colors that semantic tokens reference.
import { colorRaw } from '@ybaillet/elysium';
// Examples of raw colors
colorRaw['Cyan-500'] // '#00B6FA' - G-Hub primary
colorRaw['Teal-500'] // '#00FDCF' - Options+ primary
colorRaw['Neutral-900'] // '#1A1A1A' - Dark background
colorRaw['Neutral-100'] // '#F5F5F5' - Light background
colorRaw['Red-500'] // '#FF4D4D' - Error state
colorRaw['Green-500'] // '#00C853' - Success stateTheme Files (Split Architecture)
Each brand has its own theme file for easy management and scalability:
src/tokens/colors/
├── raw.ts # Raw color palette (shared by all themes)
├── types.ts # Shared TypeScript types and utilities
├── themeGHub.ts # G-Hub theme (Cyan primary)
├── themeOptionsPlus.ts # Options+ theme (Teal primary)
└── themes.ts # Aggregator that merges all themesTo add a new theme (e.g., themeC):
- Copy
themeGHub.tstothemeNewBrand.ts - Update the
BRAND_IDconstant - Modify color references to use your brand's colors
- Register in
themes.ts:
// themes.ts
import { themeNewBrand, BRAND_ID as NEW_BRAND_ID } from './themeNewBrand';
const THEMES = [
{ brandId: GHUB_ID, definition: themeGHub },
{ brandId: OPTIONS_PLUS_ID, definition: themeOptionsPlus },
{ brandId: NEW_BRAND_ID, definition: themeNewBrand }, // Add here
];- Add brand config to
types.ts:
// types.ts
export const BRAND_REGISTRY: BrandConfig[] = [
{ id: 'G-Hub', name: 'G-Hub', lightKey: 'G-Hub-Light', darkKey: 'G-Hub-Dark' },
{ id: 'Options+', name: 'Options+', lightKey: 'Options+-Light', darkKey: 'Options+-Dark' },
{ id: 'NewBrand', name: 'NewBrand', lightKey: 'NewBrand-Light', darkKey: 'NewBrand-Dark' }, // Add here
];Theme File Structure
Each theme file exports a ThemeDefinition with light/dark color references:
// Example from themeGHub.ts
import { ThemeDefinition, ref } from './types';
export const BRAND_ID = 'G-Hub';
export const themeGHub: ThemeDefinition = {
"surface-primary-default": {
light: ref("colors-cyan-500"), // References colorRaw['colors-cyan-500']
dark: ref("colors-cyan-500"),
},
"surface-background": {
light: ref("colors-gray-50"), // Light mode background
dark: ref("colors-gray-1000"), // Dark mode background
},
// ... more tokens
};Semantic Colors (colorsThemes)
Theme-aware tokens that reference raw colors. Each semantic token has values for all 4 theme/mode combinations: G-Hub-Light, G-Hub-Dark, Options+-Light, Options+-Dark.
import { colorsThemes, getSemanticColor } from '@ybaillet/elysium';
// Semantic token categories:
// - surface-* : Background colors (surface-primary-default, surface-secondary, surface-dialog)
// - text-* : Text colors (text-primary, text-secondary, text-neutral-muted)
// - stroke-* : Border colors (stroke-primary-default, stroke-neutral-muted)
// - icon-* : Icon colors (icon-primary, icon-secondary)
// - interactive-*: Interactive element colors
// - background-* : Page background aliases (background-1000, background-975, background-950)
// Get resolved color value for a specific theme/mode
const bgColor = getSemanticColor('surface-primary-default', 'dark', 'G-Hub');
const textColor = getSemanticColor('text-neutral-default', 'light', 'Options+');Other Tokens
import { dimensions, typography, getSpacing, getRadius } from '@ybaillet/elysium';
// Spacing tokens
getSpacing('04') // '4px'
getSpacing('08') // '8px'
getSpacing('16') // '16px'
getSpacing('24') // '24px'
// Border radius tokens
getRadius('04') // '4px'
getRadius('08') // '8px'
getRadius('16') // '16px'
getRadius('full') // '9999px'
// Typography tokens include font sizes, weights, and line heightsCSS Variables
The ThemeProvider injects CSS variables for all design tokens. You can use them directly:
.my-component {
background: var(--color-surface-primary-default);
border-radius: var(--radius-08);
padding: var(--spacing-16);
}TypeScript Support
All components export their prop types:
import type {
ButtonProps,
CardProps,
ThemeContextType,
ThemeName,
ThemeMode
} from '@ybaillet/elysium';Font Files
The BrownLogitechPan font is included with 12 weights:
- Thin (100) + Italic
- Light (300) + Italic
- Regular (400) + Italic
- Medium (500) + Italic
- Bold (700) + Italic
- Black (900) + Italic
Font files are located at @ybaillet/elysium/fonts/* if you need to reference them directly.
Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Electron (latest)
License
MIT
