Color Picker Rails Components
Add color selection functionality to your forms with Shoelace-powered color pickers, swatches, opacity controls, and multiple color formats. Perfect for theme customization, design tools, and user preferences.
Installation
1. Stimulus Controller Setup
Start by adding the following controller to your project:
import { Controller } from "@hotwired/stimulus"
// Shoelace Color Picker Controller
//
// Keeps a value display and optional hidden form input in sync with <sl-color-picker>.
// Also supports optional custom swatch buttons.
export default class extends Controller {
static targets = ["picker", "value", "input", "swatch"]
static values = {
defaultValue: { type: String, default: "#3b82f6" }
}
connect() {
this.sync(this.currentColor())
}
change(event) {
const color = event.target.value
this.sync(color)
this.dispatchChangeEvent(color)
}
selectSwatch(event) {
event.preventDefault()
const color = event.currentTarget.dataset.color
if (!color || !this.hasPickerTarget) {
return
}
this.pickerTarget.value = color
this.sync(color)
this.dispatchChangeEvent(color)
}
currentColor() {
if (this.hasPickerTarget && this.pickerTarget.value) {
return this.pickerTarget.value
}
return this.defaultValueValue
}
sync(color) {
const nextColor = color || this.defaultValueValue
if (this.hasPickerTarget && this.pickerTarget.value !== nextColor) {
this.pickerTarget.value = nextColor
}
if (this.hasValueTarget) {
this.valueTarget.textContent = nextColor
}
if (this.hasInputTarget) {
this.inputTarget.value = nextColor
}
this.updateSwatchSelection(nextColor)
}
updateSwatchSelection(selectedColor) {
const normalized = (selectedColor || "").toLowerCase()
this.swatchTargets.forEach((swatch) => {
const swatchColor = (swatch.dataset.color || "").toLowerCase()
const isSelected = swatchColor === normalized
swatch.setAttribute("aria-pressed", isSelected ? "true" : "false")
swatch.classList.toggle("ring-neutral-500", isSelected)
swatch.classList.toggle("dark:ring-neutral-400", isSelected)
swatch.classList.toggle("ring-transparent", !isSelected)
})
}
dispatchChangeEvent(color) {
const event = new CustomEvent("color-picker:change", {
bubbles: true,
detail: { color }
})
this.element.dispatchEvent(event)
}
}
2. Shoelace Color Picker
For advanced color picker features, you can use Shoelace's color picker component.
<!-- Shoelace -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/components/color-picker/color-picker.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/light.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/dark.css"
/>
3. Shoelace Styling
If you want to have more neutral accent colors instead of the default blue and more rounded corners, you can use the following CSS to style the Shoelace color picker:
/* Shoelace Neutral styles */
:root {
--sl-color-primary-50: var(--sl-color-neutral-50) !important;
--sl-color-primary-100: var(--sl-color-neutral-100) !important;
--sl-color-primary-200: var(--sl-color-neutral-200) !important;
--sl-color-primary-300: var(--sl-color-neutral-300) !important;
--sl-color-primary-400: var(--sl-color-neutral-400) !important;
--sl-color-primary-500: var(--sl-color-neutral-500) !important;
--sl-color-primary-600: var(--sl-color-neutral-600) !important;
--sl-color-primary-700: var(--sl-color-neutral-700) !important;
--sl-color-primary-800: var(--sl-color-neutral-800) !important;
--sl-color-primary-900: var(--sl-color-neutral-900) !important;
--sl-color-primary-950: var(--sl-color-neutral-950) !important;
--sl-input-focus-ring-color: hsla(0, 0%, 81%, 0.4) !important;
--sl-border-radius-medium: 0.5rem !important;
--sl-border-radius-large: 0.75rem !important;
}
Shoelace Examples
Basic color picker
A simple Shoelace color picker with HEX output.
#3b82f6
<%# Basic Shoelace Color Picker (raw Stimulus markup) %>
<div class="space-y-4" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="#3b82f6">
<div class="flex flex-col items-center gap-2">
<sl-color-picker
id="basic-picker"
label="Choose a color"
value="#3b82f6"
format="hex"
size="medium"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="flex justify-center items-center gap-3 text-sm">
<span class="text-neutral-600 dark:text-neutral-400">Current value:</span>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs" data-shoelace-color-picker-target="value">#3b82f6</code>
</div>
</div>
Color picker with swatches
Shoelace color picker with an inline swatch palette for quick selection.
#8b5cf6
<%# Shoelace Color Picker with Swatches (raw Stimulus markup) %>
<div class="space-y-4" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="#8b5cf6">
<div class="flex flex-col items-center gap-2">
<sl-color-picker
id="swatches-picker"
label="Theme Color"
value="#8b5cf6"
format="hex"
size="medium"
swatches="#ede9fe; #ddd6fe; #c4b5fd; #a78bfa; #8b5cf6; #7c3aed; #6d28d9; #5b21b6; #4c1d95"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="flex justify-center items-center gap-3 text-sm">
<span class="text-neutral-600 dark:text-neutral-400">Selected swatch:</span>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs" data-shoelace-color-picker-target="value">#8b5cf6</code>
</div>
</div>
Enhanced color picker
Shoelace color picker with a modern interface and live value display.
rgb(59, 130, 246)
<%# Enhanced Shoelace Color Picker (raw Stimulus markup) %>
<div class="space-y-4" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="rgb(59, 130, 246)">
<div class="flex flex-col items-center gap-2">
<sl-color-picker
id="enhanced-picker"
label="Brand Color"
value="rgb(59, 130, 246)"
format="rgb"
size="large"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="flex justify-center items-center gap-3 text-sm">
<span class="text-neutral-600 dark:text-neutral-400">Current value:</span>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs" data-shoelace-color-picker-target="value">rgb(59, 130, 246)</code>
</div>
</div>
Color picker with opacity
Advanced color picker with alpha channel support for transparency control.
#f5a623ff
<%# Shoelace Color Picker with Opacity (raw Stimulus markup) %>
<div class="space-y-4" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="#f5a623ff">
<div class="flex flex-col items-center gap-2">
<sl-color-picker
id="opacity-picker"
label="Background Color"
value="#f5a623ff"
format="hex"
opacity
no-format-toggle
size="medium"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="flex justify-center items-center gap-3 text-sm">
<span class="text-neutral-600 dark:text-neutral-400">Hex (with alpha):</span>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs" data-shoelace-color-picker-target="value">#f5a623ff</code>
</div>
</div>
Color format variations
Color pickers configured for different color format outputs: HEX, RGB, HSL, and HSV.
Choose the output format that best suits your needs
#4a90e2
rgb(80, 227, 194)
hsl(290, 87%, 47%)
hsv(55, 89%, 97%)
<%# Shoelace Color Picker with format variations (raw Stimulus markup) %>
<div class="space-y-6">
<p class="text-sm text-neutral-500 dark:text-neutral-400 text-center">
Choose the output format that best suits your needs
</p>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="space-y-3" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="#4a90e2">
<div class="flex flex-col items-center">
<sl-color-picker
label="HEX Format"
format="hex"
value="#4a90e2"
size="small"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="text-center">
<div class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">Output:</div>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs text-neutral-700 dark:text-neutral-300" data-shoelace-color-picker-target="value">#4a90e2</code>
</div>
</div>
<div class="space-y-3" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="rgb(80, 227, 194)">
<div class="flex flex-col items-center">
<sl-color-picker
label="RGB Format"
format="rgb"
value="rgb(80, 227, 194)"
size="small"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="text-center">
<div class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">Output:</div>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs text-neutral-700 dark:text-neutral-300" data-shoelace-color-picker-target="value">rgb(80, 227, 194)</code>
</div>
</div>
<div class="space-y-3" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="hsl(290, 87%, 47%)">
<div class="flex flex-col items-center">
<sl-color-picker
label="HSL Format"
format="hsl"
value="hsl(290, 87%, 47%)"
size="small"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="text-center">
<div class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">Output:</div>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs text-neutral-700 dark:text-neutral-300" data-shoelace-color-picker-target="value">hsl(290, 87%, 47%)</code>
</div>
</div>
<div class="space-y-3" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="hsv(55, 89%, 97%)">
<div class="flex flex-col items-center">
<sl-color-picker
label="HSV Format"
format="hsv"
value="hsv(55, 89%, 97%)"
size="small"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
</div>
<div class="text-center">
<div class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">Output:</div>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs text-neutral-700 dark:text-neutral-300" data-shoelace-color-picker-target="value">hsv(55, 89%, 97%)</code>
</div>
</div>
</div>
</div>
Color picker with custom palette
Enhanced color picker with a custom color palette for consistent brand colors.
#4a5568
Brand palette
<%# Shoelace Color Picker with custom buttons (raw Stimulus markup) %>
<div class="space-y-6" data-controller="shoelace-color-picker" data-shoelace-color-picker-default-value-value="#4a5568">
<div class="flex flex-col items-center gap-3">
<sl-color-picker
id="brand-picker"
label="Brand Color"
value="#4a5568"
format="hex"
size="medium"
data-shoelace-color-picker-target="picker"
data-action="sl-change->shoelace-color-picker#change"
></sl-color-picker>
<input
type="hidden"
name="settings[brand_color]"
value="#4a5568"
data-shoelace-color-picker-target="input"
>
<div class="text-sm text-center">
<div class="text-xs text-neutral-500 dark:text-neutral-400 mb-1">Selected Color:</div>
<code class="px-2 py-1 bg-neutral-100 dark:bg-neutral-800 rounded font-mono text-xs" data-shoelace-color-picker-target="value">#4a5568</code>
</div>
</div>
<div class="space-y-2">
<p class="text-xs text-neutral-500 dark:text-neutral-400 uppercase tracking-wide text-center">Brand palette</p>
<div class="flex justify-center gap-2 flex-wrap">
<% ["#1a202c", "#2d3748", "#4a5568", "#718096", "#a0aec0", "#e53e3e", "#c53030", "#742a2a"].each do |color| %>
<% selected_classes = color.casecmp?("#4a5568") ? "ring-neutral-500 dark:ring-neutral-400" : "ring-transparent" %>
<button
type="button"
class="h-8 w-8 rounded-md border border-black/15 dark:border-white/20 transition-transform hover:scale-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 dark:focus-visible:outline-neutral-200 ring-2 ring-offset-2 dark:ring-offset-neutral-900 <%= selected_classes %>"
style="background-color: <%= color %>;"
data-color="<%= color %>"
data-shoelace-color-picker-target="swatch"
data-action="click->shoelace-color-picker#selectSwatch"
aria-label="Pick <%= color %>"
aria-pressed="<%= color.casecmp?("#4a5568") ? "true" : "false" %>"
></button>
<% end %>
</div>
</div>
</div>
Configuration
Shoelace color picker supports multiple formats, opacity, custom swatches, and events out of the box.
Basic Shoelace Usage
Minimal setup for a color input:
<sl-color-picker
label="Brand Color"
value="#3b82f6"
format="hex"
size="medium"
></sl-color-picker>
With Custom Swatches
Provide brand or preset colors directly on the picker:
<sl-color-picker
label="Theme Color"
value="#8b5cf6"
swatches="#8b5cf6; #3b82f6; #10b981"
></sl-color-picker>
Shoelace Color Picker Options
Available attributes for Shoelace color picker customization:
| Attribute | Default | Description |
|---|---|---|
value |
#ffffff | The current color value |
format |
hex | Output format: hex, rgb, hsl, or hsv |
opacity |
false | Show opacity slider for alpha channel |
swatches |
"" | Custom color swatches (semicolon-separated) |
label |
"" | Label text for the color picker |
size |
medium | Size variant: small, medium, or large |
JavaScript Events
Handle color selection events:
// Shoelace native event
document.querySelector('sl-color-picker').addEventListener('sl-change', (e) => {
console.log('Color changed:', e.target.value);
});
// Rails Blocks wrapper event (shared partial and ViewComponent)
document.addEventListener('color-picker:change', (e) => {
console.log('Color changed:', e.detail.color);
});
Accessibility
- Keyboard Support: Shoelace color picker supports keyboard interaction and focus management
- Screen Readers: Use proper labels and aria-labels
- Color Contrast: Ensure selected colors meet WCAG contrast requirements
Browser Support
- Shoelace: Requires modern browsers with Web Components support
- Fallback: Add a hidden/text fallback only if you need legacy browser support