Tucano v0.37.2

Color picker

Um campo de cor com área de saturação e brilho, matiz, opacidade, paleta e conta-gotas. O <input type="text"> continua guardando o valor e o name, então o formulário posta #4f46e5 e o hex continua digitável para quem já sabe a cor.

Exemplos

O mesmo <input> com atributos diferentes. O painel abre pela amostra à esquerda do campo.

Sem valor

Campo sem value nasce vazio, sem cor escolhida por você, e o required barra o envio até alguém escolher.

Sem opacidade

data-alpha="false" tira a trilha; o hex fica sempre com 6 dígitos.

Formato hsl

data-format="hsl"; com opacidade abaixo de 1, sai hsla().

Paleta própria

data-swatches com as cores da marca, separadas por vírgula.

Sem paleta

data-swatches="false" deixa só a área, as trilhas e o valor.

Painel alinhado à esquerda

data-placement="bottom-start". As bordas da tela continuam mandando.

Com erro

aria-invalid="true" no campo — o Django 5 já escreve.

Essa cor já está em uso por outra etiqueta.

Como usar

Marque o <input> e ele inicializa sozinho no carregamento e a cada htmx:afterSwap. A amostra e o valor viram um controle só, .tuc-color-field, com a altura, o raio e o anel de foco do 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">
AtributoPadrãoPara quê
data-formathexFormato do valor: hex, rgb ou hsl
data-alphatruefalse tira a trilha de opacidade
data-swatchespaleta de 15 coresCores separadas por vírgula, ou false para esconder
data-placementbottom-centerLado e alinhamento do painel

No formulário do Django

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

Um CharField(max_length=9) cabe o hex com opacidade (#rrggbbaa). Use TextInput, e não type="color": o nativo não tem opacidade e mudaria o desenho do campo.

Em 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; texto que não é cor devolve false e nada muda
c.open();
c.destroy();

Valor e formulário

Quem posta é o seu <input>, com o name dele. O texto fica no formato pedido, qualquer que seja a notação usada para escolher.

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

Na entrada, digitada no campo ou no painel, valem #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(), rgba(), hsl() e hsla(). O texto é lido ao confirmar — sair do campo ou Enter — e texto que não é cor volta para o valor atual, em vez de zerar a cor.

Cada mudança dispara tucano:change no campo, e também o change nativo, para validação e HTMX. No arrasto, como no <input type="range">, o tucano:change sai a cada movimento e o change nativo uma vez só, ao 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;   // o ColorPicker
});

Para escolher texto claro ou escuro sobre a cor que a pessoa escolheu, Tucano.color.isDark('#4f46e5') devolve true — ele mede a luminância, e não a média dos canais, que o olho não lê assim.

O estado guardado é HSVA, e não RGB

Converter para RGB a cada movimento perde a matiz quando a saturação chega a zero: todo cinza viraria vermelho ao clarear de novo. Guardando matiz, saturação, brilho e opacidade, arrastar até o branco e voltar devolve a cor de onde se saiu.

Conta-gotas só onde o navegador tem

O botão de capturar cor da tela aparece quando existe a API EyeDropper — hoje, Chrome e Edge no desktop. Nos outros ele simplesmente não é desenhado, em vez de ficar lá sem funcionar.

Teclado e acessibilidade

O gatilho é a amostra ao lado do campo, um <button> de verdade, com aria-haspopup="dialog" e aria-expanded. Chegar de Tab não abre nada.

TeclaOndeAção
Enter EspaçoAmostraAbre o painel com o foco na área, e fecha
↓Amostra ou campoAbre o painel com o foco na área
← →ÁreaSaturação, de 2 em 2%; com Shift, de 10 em 10
↑ ↓ÁreaBrilho, no mesmo passo
← → ↑ ↓TrilhasMatiz de 1 em 1 grau, opacidade de 1 em 1%; com Shift, de 10 em 10
Home EndTrilhasMínimo e máximo
EscPainelFecha e devolve o foco à amostra

Por que o foco no campo não abre o painel

Abrir no foco atrapalhava duas vezes: o painel subia só de tabular pelo formulário, e cobria o próprio campo de quem queria digitar o hex. Por isso o gatilho é a amostra, que já responde a Enter e Espaço por ser botão, e no campo fica só a seta para baixo, a mesma do date picker.

As trilhas são role="slider" com aria-valuenow, a área tem rótulo "Saturação e brilho", e o valor no painel é um .tuc-input comum. O painel fecha quando o foco sai dele ou com clique fora; com prefers-reduced-motion, só esmaece.

API

Gerada do código a cada build — se algo não está aqui, não existe.

Marcação
[data-tuc-color]
Em 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

Opções

OpçãoPadrãoPara 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