Tucano v0.37.2

Color picker

Un campo de color con área de saturación y brillo, tono, opacidad, paleta y cuentagotas. El <input type="text"> sigue guardando el valor y el name, así que el formulario envía #4f46e5 y el hex se puede seguir escribiendo si ya sabes el color.

Ejemplos

El mismo <input> con atributos distintos. El panel se abre desde la muestra a la izquierda del campo.

Sin valor

Un campo sin value nace vacío, sin ningún color elegido por ti, y required bloquea el envío hasta que alguien elija uno.

Sin opacidad

data-alpha="false" quita la barra; el hex siempre tiene 6 dígitos.

Formato hsl

data-format="hsl"; con opacidad menor que 1, sale hsla().

Paleta propia

data-swatches con los colores de la marca, separados por comas.

Sin paleta

data-swatches="false" deja solo el área, las barras y el valor.

Panel alineado a la izquierda

data-placement="bottom-start". Los bordes de la pantalla siguen mandando.

Con error

aria-invalid="true" en el campo — Django 5 ya lo escribe.

Otra etiqueta ya usa este color.

Cómo usar

Marca el <input> y se inicializa solo al cargar la página y en cada htmx:afterSwap. La muestra y el valor se convierten en un único control, .tuc-color-field, con la altura, el radio y el anillo de foco del Select.

<input type="text" name="color" value="#4f46e5" data-tuc-color>
<input type="text" name="brand" value="#0d9488" data-tuc-color data-alpha="false"
       data-swatches="#0a0a0a,#ea580c,#16a34a">
AtributoPor defectoPara qué
data-formathexFormato del valor: hex, rgb o hsl
data-alphatruefalse quita la barra de opacidad
data-swatchespaleta de 15 coloresColores separados por comas, o false para ocultarla
data-placementbottom-centerLado y alineación del panel

En el formulario de Django

class TagForm(forms.ModelForm):
    class Meta:
        model = Tag
        fields = ["name", "color"]
        widgets = {
            "color": forms.TextInput(attrs={"data-tuc-color": "", "data-alpha": "false"}),
        }

Un CharField(max_length=9) alcanza para el hex con opacidad (#rrggbbaa). Usa TextInput, y no type="color": el nativo no tiene opacidad y cambiaría el aspecto del campo.

En JavaScript

const c = new Tucano.ColorPicker('#color', {
  format: 'rgb',
  alpha: false,
  swatches: ['#0a0a0a', '#ea580c', '#16a34a'],
  onChange: (value, { rgb, hsva }) => console.log(value),
});

c.getValue();           // 'rgb(79, 70, 229)'
c.getRgb();             // { r: 79, g: 70, b: 229, a: 1 }
c.setValue('#16a34a');  // true; un texto que no es color devuelve false y no cambia nada
c.open();
c.destroy();

Valor y formulario

Quien envía es tu propio <input>, con su name. El texto queda en el formato pedido, sea cual sea la notación usada para elegir.

FormatoOpacoCon opacidad
hex#4f46e5#4f46e599
rgbrgb(79, 70, 229)rgba(79, 70, 229, 0.6)
hslhsl(243, 75%, 59%)hsla(243, 75%, 59%, 0.6)

Como entrada, escrita en el campo o en el panel, valen #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(), rgba(), hsl() y hsla(). El texto se lee al confirmar — salir del campo o Enter — y un texto que no es color vuelve al valor actual, en lugar de borrar el color.

Cada cambio dispara tucano:change en el campo, y también el change nativo, para la validación y HTMX. Al arrastrar, como en el <input type="range">, el tucano:change sale en cada movimiento y el change nativo una sola vez, al soltar.

document.querySelector('#color').addEventListener('tucano:change', (e) => {
  e.detail.value;      // '#4f46e5'
  e.detail.rgb;        // { r, g, b, a }
  e.detail.hsva;       // { h, s, v, a }
  e.detail.instance;   // el ColorPicker
});

Para elegir texto claro u oscuro sobre el color que la persona eligió, Tucano.color.isDark('#4f46e5') devuelve true — mide la luminancia, y no el promedio de los canales, que no es como lo lee el ojo.

El estado guardado es HSVA, y no RGB

Convertir a RGB en cada movimiento pierde el tono cuando la saturación llega a cero: todo gris se volvería rojo al aclararlo de nuevo. Guardando tono, saturación, brillo y opacidad, arrastrar hasta el blanco y volver devuelve el color del que se partió.

Cuentagotas solo donde el navegador lo tiene

El botón para capturar un color de la pantalla aparece cuando existe la API EyeDropper — hoy, Chrome y Edge en escritorio. En los demás simplemente no se dibuja, en lugar de quedarse ahí sin funcionar.

Teclado y accesibilidad

El disparador es la muestra junto al campo, un <button> de verdad, con aria-haspopup="dialog" y aria-expanded. Llegar con Tab no abre nada.

TeclaDóndeAcción
Enter EspacioMuestraAbre el panel con el foco en el área, y lo cierra
↓Muestra o campoAbre el panel con el foco en el área
← →ÁreaSaturación, de 2 en 2%; con Shift, de 10 en 10
↑ ↓ÁreaBrillo, con el mismo paso
← → ↑ ↓BarrasTono de 1 en 1 grado, opacidad de 1 en 1%; con Shift, de 10 en 10
Home EndBarrasMínimo y máximo
EscPanelCierra y devuelve el foco a la muestra

Por qué el foco en el campo no abre el panel

Abrir con el foco molestaba dos veces: el panel aparecía solo con tabular por el formulario, y tapaba el propio campo a quien quería escribir el hex. Por eso el disparador es la muestra, que ya responde a Enter y Espacio por ser un botón, y en el campo queda solo la flecha hacia abajo, la misma del date picker.

Las barras son role="slider" con aria-valuenow, el área tiene la etiqueta "Saturação e brilho" (saturación y brillo — las etiquetas por defecto están en portugués, y Tucano.setTexts({ colorpicker }) las reemplaza), y el valor en el panel es un .tuc-input común. El panel se cierra cuando el foco sale de él o con un clic afuera; con prefers-reduced-motion, solo se desvanece.

API

Generada a partir del código en cada build — si algo no está aquí, no existe.

Marcado
[data-tuc-color]
En JS
new Tucano.ColorPicker(alvo, opcoes)
Atributos
data-alpha data-format data-placement data-swatches
Métodos
getValue getRgb setValue open close toggle destroy
Eventos
tucano:change

Opciones

Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.

OpciónPor defectoPara qué
format'hex''hex' | 'rgb' | 'hsl'
alphatrue
swatchesPALETTEfalse desliga
placement'bottom-center'mesma regra do date picker: centralizado, preso na borda da tela
appendToundefined
onChangenull