Advanced Topics
Accessibility
Section titled “Accessibility”The <color-input> component is built with accessibility as a core requirement, following WAI-ARIA Authoring Practices Guide (APG) patterns.
Keyboard Support
Section titled “Keyboard Support”All functionality is accessible via keyboard:
Opening/Closing:
- Enter or Space on the trigger button - Opens the picker
- Escape - Closes the picker and returns focus to the trigger
- Tab - Navigates between controls when open
Adjusting Colors:
- Arrow Up/Down - Increment/decrement value by 1 unit
- Shift + Arrow Up/Down - Increment/decrement by 10 units
- Home - Set to minimum value
- End - Set to maximum value
- Tab - Move between channel controls
Try keyboard navigation: Tab to focus, Enter to open, Arrow keys to adjust sliders, Escape to close.
- Value
- oklch(70% 0.2 240)
- Color Space
- oklch
- Gamut
- —
- Contrast Color
-
—
View Code
<color-input value="oklch(70% 0.2 240)" colorspace="oklch" theme="auto"></color-input>Screen Reader Support
Section titled “Screen Reader Support”The component uses semantic HTML with proper ARIA attributes:
- Semantic Elements: Native
<button>,<input type="range">,<input type="number"> - ARIA Labels:
aria-labelfor control purpose (e.g., “Lightness slider”) - Live Regions:
aria-live="polite"for value announcements - Range Properties:
aria-valuenow,aria-valuemin,aria-valuemax
Focus Management
Section titled “Focus Management”Focus Rings:
All interactive elements have visible focus indicators using :focus-visible.
Focus Trap: When the picker is open, Tab cycles through controls within the popover. Escape closes and returns focus to the trigger.
Focus Restoration: Focus returns to the trigger button when the picker closes.
Contrast & Color
Section titled “Contrast & Color”The contrastColor property provides the recommended text color for maximum contrast:
const picker = document.querySelector('color-input');
picker.addEventListener('change', (e) => { const textColor = picker.contrastColor; // "white" or "black" document.body.style.background = e.detail.value; document.body.style.color = textColor;});The component calculates contrast using WCAG 2.1 formulas for AA/AAA compliance.
Touch & Mobile
Section titled “Touch & Mobile”Hit Target Sizing:
- Mobile: ≥44px × 44px
- Desktop: ≥24px × 24px
Touch Affordances:
touch-action: manipulationprevents double-tap zoom- Generous padding expands clickable areas
- No hover-only interactions
Reduced Motion
Section titled “Reduced Motion”Respects prefers-reduced-motion for users sensitive to motion:
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }}Best Practices
Section titled “Best Practices”Testing Checklist
Section titled “Testing Checklist”- ☐ All features work with keyboard only
- ☐ Screen reader announces changes properly
- ☐ Focus indicators are visible
- ☐ Hit targets meet minimum sizes
- ☐ Color contrast meets WCAG AA (4.5:1 for text)
- ☐ Works with reduced motion enabled
- ☐ Tab order is logical
- ☐ Popover closes with Escape
Architecture
Section titled “Architecture”Signals-Based Reactivity
Section titled “Signals-Based Reactivity”The component uses Preact Signals for efficient, fine-grained reactivity:
- Minimal Re-renders: Only affected parts of the UI update
- Automatic Dependency Tracking: Signals track dependencies automatically
- Lightweight: Small runtime overhead
Shadow DOM Encapsulation
Section titled “Shadow DOM Encapsulation”The component uses Shadow DOM for style isolation:
- No Style Conflicts: Component styles don’t leak to the page
- CSS Parts API: Customize via
::part()selectors - Composability: Use multiple instances without conflicts
Color Math
Section titled “Color Math”Color calculations are powered by colorjs.io:
- Accurate Conversions: Between all supported color spaces
- Gamut Mapping: Automatic handling of out-of-gamut colors
- Standards Compliant: Follows CSS Color Module Level 4
Performance
Section titled “Performance”Bundle Size
Section titled “Bundle Size”The component is designed for minimal bundle impact:
- Tree-Shakeable: ES modules allow unused code elimination
- No Framework Dependencies: Only Preact Signals and colorjs.io
- Modern Output: Targets ES2020+ for smaller bundles
Runtime Performance
Section titled “Runtime Performance”Efficient Updates:
- Signals prevent unnecessary re-renders
- Color conversions are cached
- Popover uses native Popover API (hardware accelerated)
Lazy Loading:
- Component registers on import
- Popover content rendered on-demand
Popover Positioning
Section titled “Popover Positioning”The component uses the native Popover API with intelligent positioning:
Automatic Placement
Section titled “Automatic Placement”The popover automatically positions relative to the trigger button:
- Respects viewport boundaries
- Handles safe areas (notches, system UI)
- Flips when near edges
- No manual positioning required
Custom Anchoring
Section titled “Custom Anchoring”Use setAnchor() to position relative to a different element:
const picker = document.querySelector('color-input');const customAnchor = document.querySelector('#my-anchor');
picker.setAnchor(customAnchor);picker.show();Browser Support
Section titled “Browser Support”The Popover API is required:
- Chrome: 114+
- Safari: 17+
- Firefox: 125+
Gamut Detection
Section titled “Gamut Detection”The component automatically detects which color gamut contains the current color:
Gamut Property
Section titled “Gamut Property”The read-only gamut property returns the smallest gamut:
console.log(picker.gamut); // "srgb" | "p3" | "rec2020" | "xyz"Visual Badge
Section titled “Visual Badge”A gamut badge appears in the picker UI showing the current gamut. This helps you understand:
- srgb: Works on all displays
- p3: Requires Display P3 or better
- rec2020: Requires ultra-wide gamut
- xyz: Beyond Rec2020
Gamut in Events
Section titled “Gamut in Events”The change event includes gamut information:
picker.addEventListener('change', (e) => { console.log(e.detail.gamut); // "srgb" | "p3" | "rec2020" | "xyz"});Form Integration
Section titled “Form Integration”While the component doesn’t inherit from HTMLInputElement, you can integrate it with forms:
Hidden Input Pattern
Section titled “Hidden Input Pattern”<form id="settings-form"> <label for="theme-color">Theme Color</label> <color-input id="theme-color" value="oklch(70% 0.2 240)"></color-input> <input type="hidden" name="theme_color" id="theme-color-input"></form>
<script> const picker = document.querySelector('#theme-color'); const hiddenInput = document.querySelector('#theme-color-input');
picker.addEventListener('change', (e) => { hiddenInput.value = e.detail.value; });
// Set initial value hiddenInput.value = picker.value;</script>Programmatic Control
Section titled “Programmatic Control”Use properties and methods to control the picker:
const picker = document.querySelector('color-input');
// Read stateconsole.log(picker.value);console.log(picker.colorspace);console.log(picker.gamut);console.log(picker.contrastColor);
// Update statepicker.value = 'oklch(80% 0.15 180)';picker.colorspace = 'hsl';picker.theme = 'dark';
// Control popoverpicker.show();picker.close();Resources
Section titled “Resources”Specifications
Section titled “Specifications”- colorjs.io - Color science library
- WebAIM Contrast Checker
- OKLCH Color Picker