Como criar um componente de botão

Uma visão geral básica sobre como criar componentes <button> adaptáveis a cores, responsivos e acessíveis.

Nesta postagem, quero compartilhar meus pensamentos sobre como criar uma coleção de cores um elemento <button> responsivo e acessível. Experimente a demonstração e veja o fonte

Nos temas claro e escuro, é possível interagir com os botões usando o teclado e o mouse.

Se você preferir o vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Compatibilidade com navegadores

  • Chrome: 1.
  • Borda: 12.
  • Firefox: 1.
  • Safari: 1.

Origem

A <button> é criado para interação do usuário. Os gatilhos de evento click do teclado, mouse, toque, voz e muito mais, com regras inteligentes sobre tempo. Ele também vem com alguns estilos padrão em cada navegador, para que você possa usá-los diretamente sem qualquer personalização. Use color-scheme para ativar nos botões claro e escuro do navegador.

Há também diferentes tipos de botões, cada uma mostrada na incorporação anterior do Codepen. Uma <button> sem um tipo vai se adaptam a uma <form>, mudando para o tipo de envio.

<!-- buttons -->
<button></button>
<button type="submit"></button>
<button type="button"></button>
<button type="reset"></button>

<!-- button state -->
<button disabled></button>

<!-- input buttons -->
<input type="button" />
<input type="file">

No Desafio da GUI deste mês, cada botão receberá estilos para ajudar a diferenciar visualmente a intenção. Redefinir botões terão cores de aviso, pois são destrutivas, e envie botões receberá texto destacado em azul para que pareçam um pouco mais promovidos do que o normal botões.

Visualização do conjunto final de todos os tipos de botões, mostrados em um formulário e não em um formulário, com boas adições para botões de ícone e personalizados.
Visualização do conjunto final de todos os tipos de botão, mostrados em um formulário, e não em um formulário. com boas adições para botões de ícone e botões personalizados

Os botões também têm pseudoclasses que o CSS usará para definir o estilo. Essas classes fornecem ganchos CSS para personalizar a sensação do botão: :hover para quando um mouse estiver sobre o botão, :active para quando um mouse ou o teclado estiver pressionando :focus ou :focus-visible para ajudar no estilo de tecnologia assistiva.

button:hover {}
button:active {}
button:focus {}
button:focus-visible {}
Visualização do conjunto final de todos os tipos de botão no tema escuro.
Prévia do conjunto final de todos os tipos de botão no tema escuro

Marcação

Além dos tipos de botão fornecidos pela especificação HTML, eu adicionei um botão com um ícone e um botão com uma classe personalizada btn-custom.

<button>Default</button>
<input type="button" value="<input>"/>
<button>
  <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
    <path d="..." />
  </svg>
  Icon
</button>
<button type="submit">Submit</button>
<button type="button">Type Button</button>
<button type="reset">Reset</button>
<button disabled>Disabled</button>
<button class="btn-custom">Custom</button>
<input type="file">

Para testar, cada botão é colocado dentro de um formulário. Assim, posso garantir estilos são atualizados adequadamente para o botão padrão, que se comporta como um botão "Enviar". Também mudo a estratégia de ícones, de SVG inline para SVG mascarado, para garantir que ambos funcionem igualmente bem.

<form>
  <button>Default</button>
  <input type="button" value="<input>"/>
  <button>Icon <span data-icon="cloud"></span></button>
  <button type="submit">Submit</button>
  <button type="button">Type Button</button>
  <button type="reset">Reset</button>
  <button disabled>Disabled</button>
  <button class="btn-custom btn-large" type="button">Large Custom</button>
  <input type="file">
</form>

A matriz de combinações é bastante complexa neste ponto. Botão "Entre" tipos, pseudoclasses e estar dentro ou fora de uma forma, há mais de 20 combinações de botões. É uma boa coisa que o CSS possa nos ajudar a articular cada um com clareza.

Acessibilidade

Os elementos de botão são naturalmente acessíveis, mas há alguns comuns melhorias.

Passar o cursor e focar juntos

Quero agrupar :hover e :focus com o :is() funcional. pseudoseletor. Isso ajuda a garantir que minhas interfaces sempre considerem o teclado e estilos de tecnologia assistiva.

button:is(:hover, :focus) {
  
}
Confira uma demonstração!

Anel de foco interativo

Gosto de animar o anel de foco para usuários de teclado e de tecnologia assistiva. eu fazer isso animando o contorno do botão em 5px, mas apenas quando o botão não está ativo. Isso cria um efeito que faz com que o anel de foco encolher de volta para o tamanho do botão quando pressionado.

:where(button, input):where(:not(:active)):focus-visible {
  outline-offset: 5px;
}

Garantir um contraste de cor passageiro

Há pelo menos quatro combinações de cores diferentes entre claro e escuro que precisam considerar o contraste de cores: botão, botão enviar, botão de redefinição e desativado. O VisBug é usado aqui para inspecionar e mostrar todas as pontuações de uma vez:

Ocultando ícones de pessoas que não podem ver

Ao criar um botão de ícone, o ícone deve oferecer suporte visual ao texto do botão. Isso também significa que o ícone não tem valor para alguém com visão e perda de talentos. Felizmente, o navegador oferece uma maneira de ocultar itens do leitor de tela tecnologia para que pessoas com perda de visão não se incomodem com botões decorativos imagens:

<button>
  <svg … aria-hidden="true">...</svg>
  Icon Button
</button>
Chrome DevTools mostrando a árvore de acessibilidade do botão. A árvore ignora a imagem do botão porque ele tem aria-hidden definido como true.
Chrome DevTools mostrando a árvore de acessibilidade do botão. A árvore ignora a imagem do botão porque ele tem aria-hidden definido como true
.

Estilos

Na próxima seção, primeiro estabeleço um sistema de propriedades personalizadas para gerenciar os estilos adaptáveis do botão. Com essas propriedades personalizadas, posso começar a selecionar elementos e personalizar sua aparência.

Uma estratégia de propriedade personalizada adaptável

A estratégia de propriedade personalizada usada no desafio da GUI é muito parecida com essa usado na criação de um esquema de cores. Para um sistema adaptável de cores claras e escuras, uma propriedade personalizada para cada tema é definidos e nomeados de acordo com eles. Em seguida, uma única propriedade personalizada é usada para conter o valor atual do tema e é atribuído a uma propriedade CSS. Mais tarde, o single propriedade personalizada poderá ser atualizada para um valor diferente e, em seguida, atualizar o botão estilo.

button {
  --_bg-light: white;
  --_bg-dark: black;
  --_bg: var(--_bg-light);

  background-color: var(--_bg);
}

@media (prefers-color-scheme: dark) {
  button {
    --_bg: var(--_bg-dark);
  }
}

O que eu gosto é que os temas claro e escuro são declarativos e claros. A indireção e abstração são descarregadas na propriedade personalizada --_bg, que agora é o único grupo "reativo" propriedade --_bg-light e --_bg-dark estão estática. Também está claro que o tema claro é o tema padrão e "dark" só é aplicado condicionalmente.

Como se preparar para a consistência do design

O seletor compartilhado

O seletor a seguir é usado para segmentar todos os vários tipos de botões e é um pouco exaustiva no início. :where() é usado, portanto, a personalização do botão não requer especificidade. Botões são muitas vezes adaptado para cenários alternativos, e o seletor :where() garante que a tarefa é fácil. Dentro de :where(), cada tipo de botão é selecionado, incluindo o ::file-selector-button, que não podem ser usado em :is() ou :where().

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  
}

Todas as propriedades personalizadas terão o escopo dentro desse seletor. Hora de revisar todas as propriedades personalizadas. Há várias propriedades personalizadas usadas neste . Vou descrever cada grupo à medida que avançamos, depois compartilhar contextos de movimento no final da seção.

Cor de destaque do botão

Botões e ícones de envio são um ótimo lugar para dar um toque de cor:

--_accent-light: hsl(210 100% 40%);
--_accent-dark: hsl(210 50% 70%);
--_accent: var(--_accent-light);

Cor do texto do botão

As cores do texto do botão não são branco ou preto, são versões escuras ou claras de --_accent em hsl() e mantendo o matiz 210:

--_text-light: hsl(210 10% 30%);
--_text-dark: hsl(210 5% 95%);
--_text: var(--_text-light);

Cor do plano de fundo do botão

Os planos de fundo do botão seguem o mesmo padrão hsl(), exceto pelo tema claro. -os são definidos como brancos para que sua superfície faça com que pareçam próximos ao ou na frente de outras superfícies:

--_bg-light: hsl(0 0% 100%);
--_bg-dark: hsl(210 9% 31%);
--_bg: var(--_bg-light);

Plano de fundo do botão

Essa cor de fundo faz uma superfície aparecer atrás de outras, útil para o plano de fundo da entrada do arquivo:

--_input-well-light: hsl(210 16% 87%);
--_input-well-dark: hsl(204 10% 10%);
--_input-well: var(--_input-well-light);

Padding do botão

O espaçamento ao redor do texto no botão é feito com a função ch , um comprimento relativo ao tamanho da fonte. Isso se torna fundamental quando grandes os botões podem simplesmente aumentar a font-size, e as escalas dos botões proporcionalmente:

--_padding-inline: 1.75ch;
--_padding-block: .75ch;

Borda do botão

O raio da borda do botão é escondido em uma propriedade personalizada para que a entrada do arquivo possa corresponder aos outros botões. As cores da borda seguem a cor adaptável estabelecida. sistema:

--_border-radius: .5ch;

--_border-light: hsl(210 14% 89%);
--_border-dark: var(--_bg-dark);
--_border: var(--_border-light);

Efeito de destaque ao passar o cursor sobre o botão

Essas propriedades estabelecem uma propriedade de tamanho para transição na interação e a cor de destaque segue o sistema de cores adaptáveis. Abordaremos como essas interagir mais tarde nesta postagem, mas elas são usadas para um box-shadow efeito:

--_highlight-size: 0;

--_highlight-light: hsl(210 10% 71% / 25%);
--_highlight-dark: hsl(210 10% 5% / 25%);
--_highlight: var(--_highlight-light);

Sombra do texto do botão

Cada botão tem um estilo sutil de sombra de texto. Isso ajuda o texto a se sobrepor o botão, melhorando a legibilidade e adicionando uma boa camada de esmalte na apresentação.

--_ink-shadow-light: 0 1px 0 var(--_border-light);
--_ink-shadow-dark: 0 1px 0 hsl(210 11% 15%);
--_ink-shadow: var(--_ink-shadow-light);

Ícone de botão

Os ícones têm o tamanho de dois caracteres graças ao comprimento relativo ch da unidade novamente, o que ajudará o ícone a dimensionar proporcionalmente ao texto do botão. A A cor do ícone depende do --_accent-color para um conteúdo adaptável e dentro do tema cor

--_icon-size: 2ch;
--_icon-color: var(--_accent);

Sombra do botão

Para que as sombras se adaptem adequadamente ao claro e ao escuro, elas precisam mudar cor e opacidade. Sombras no tema claro funcionam melhor quando são sutis e coloridas em direção à cor da superfície que se sobrepõem. As sombras do tema escuro precisam ser mais escuras mais saturadas para que possam sobrepor cores de superfície mais escuras.

--_shadow-color-light: 220 3% 15%;
--_shadow-color-dark: 220 40% 2%;
--_shadow-color: var(--_shadow-color-light);

--_shadow-strength-light: 1%;
--_shadow-strength-dark: 25%;
--_shadow-strength: var(--_shadow-strength-light);

Com cores e intensidades adaptáveis, posso montar duas profundidades de sombras:

--_shadow-1: 0 1px 2px -1px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 9%));

--_shadow-2: 
  0 3px 5px -2px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 3%)),
  0 7px 14px -5px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 5%));

Além disso, para dar aos botões uma aparência ligeiramente 3D, uma sombra de caixa 1px cria a ilusão:

--_shadow-depth-light: 0 1px var(--_border-light);
--_shadow-depth-dark: 0 1px var(--_bg-dark);
--_shadow-depth: var(--_shadow-depth-light);

Transições de botões

Seguindo o padrão de cores adaptáveis, crio duas propriedades estáticas para manter as opções do sistema de design:

--_transition-motion-reduce: ;
--_transition-motion-ok:
  box-shadow 145ms ease,
  outline-offset 145ms ease
;
--_transition: var(--_transition-motion-reduce);

Todas as propriedades juntas no seletor

Todas as propriedades personalizadas em um seletor

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  --_accent-light: hsl(210 100% 40%);
  --_accent-dark: hsl(210 50% 70%);
  --_accent: var(--_accent-light);

--_text-light: hsl(210 10% 30%); --_text-dark: hsl(210 5% 95%); --_text: var(--_text-light);

--_bg-light: hsl(0 0% 100%); --_bg-dark: hsl(210 9% 31%); --_bg: var(--_bg-light);

--_input-well-light: hsl(210 16% 87%); --_input-well-dark: hsl(204 10% 10%); --_input-well: var(--_input-well-light);

--_padding-inline: 1.75ch; --_padding-block: .75ch;

--_border-radius: .5ch; --_border-light: hsl(210 14% 89%); --_border-dark: var(--_bg-dark); --_border: var(--_border-light);

--_highlight-size: 0; --_highlight-light: hsl(210 10% 71% / 25%); --_highlight-dark: hsl(210 10% 5% / 25%); --_highlight: var(--_highlight-light);

--_ink-shadow-light: 0 1px 0 hsl(210 14% 89%); --_ink-shadow-dark: 0 1px 0 hsl(210 11% 15%); --_ink-shadow: var(--_ink-shadow-light);

--_icon-size: 2ch; --_icon-color-light: var(--_accent-light); --_icon-color-dark: var(--_accent-dark); --_icon-color: var(--accent, var(--_icon-color-light));

--_shadow-color-light: 220 3% 15%; --_shadow-color-dark: 220 40% 2%; --_shadow-color: var(--_shadow-color-light); --_shadow-strength-light: 1%; --_shadow-strength-dark: 25%; --_shadow-strength: var(--_shadow-strength-light); --_shadow-1: 0 1px 2px -1px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 9%)); --_shadow-2: 0 3px 5px -2px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 3%)), 0 7px 14px -5px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 5%)) ;

--_shadow-depth-light: hsl(210 14% 89%); --_shadow-depth-dark: var(--_bg-dark); --_shadow-depth: var(--_shadow-depth-light);

--_transition-motion-reduce: ; --_transition-motion-ok: box-shadow 145ms ease, outline-offset 145ms ease ; --_transition: var(--_transition-motion-reduce); }

Os botões padrão aparecem lado a lado nos temas claro e escuro.

Adaptações para o tema escuro

O valor dos padrões de propriedades estáticas -light e -dark fica claro quando os acessórios do tema escuro estão definidos:

@media (prefers-color-scheme: dark) {
  :where(
    button,
    input[type="button"],
    input[type="submit"],
    input[type="reset"],
    input[type="file"]
  ),
  :where(input[type="file"])::file-selector-button {
    --_bg: var(--_bg-dark);
    --_text: var(--_text-dark);
    --_border: var(--_border-dark);
    --_accent: var(--_accent-dark);
    --_highlight: var(--_highlight-dark);
    --_input-well: var(--_input-well-dark);
    --_ink-shadow: var(--_ink-shadow-dark);
    --_shadow-depth: var(--_shadow-depth-dark);
    --_shadow-color: var(--_shadow-color-dark);
    --_shadow-strength: var(--_shadow-strength-dark);
  }
}

Além de ler bem, os consumidores desses botões personalizados podem usar o objetos com confiança de que eles se adaptarão adequadamente às preferências do usuário.

Redução das adaptações de movimento

Se o usuário visitante não tiver problemas de movimento, atribuir --_transition a var(--_transition-motion-ok):

@media (prefers-reduced-motion: no-preference) {
  :where(
    button,
    input[type="button"],
    input[type="submit"],
    input[type="reset"],
    input[type="file"]
  ),
  :where(input[type="file"])::file-selector-button {
    --_transition: var(--_transition-motion-ok);
  }
}

Alguns estilos compartilhados

Botões e entradas precisam ter as fontes definidas como inherit para que correspondam ao o restante das fontes da página, caso contrário, eles serão estilizados pelo navegador. Isso também se aplica a letter-spacing. Definir line-height como 1.5 define o box com letras para dar espaço ao texto acima e abaixo:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  /* …CSS variables */

  font: inherit;
  letter-spacing: inherit;
  line-height: 1.5;
  border-radius: var(--_border-radius);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Como definir o estilo dos botões

Ajuste do seletor

O seletor input[type="file"] não é o botão da entrada, o O pseudoelemento ::file-selector-button é, então removi input[type="file"] da lista:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  
}

Ajustes de cursor e toque

Primeiro, estilo o cursor para o estilo pointer, que ajuda o botão a indicar para mostrar aos usuários que ele é interativo. Depois, adiciono touch-action: manipulation fazer com que os cliques não precisem esperar e observar um clique duplo em potencial, tornando o ficam mais rápidos:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  cursor: pointer;
  touch-action: manipulation;
}

Cores e bordas

Em seguida, personalizo o tamanho da fonte, o plano de fundo, o texto e as cores da borda, usando alguns das propriedades personalizadas adaptáveis estabelecidas anteriormente:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  font-size: var(--_size, 1rem);
  font-weight: 700;
  background: var(--_bg);
  color: var(--_text);
  border: 2px solid var(--_border);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Sombras

Os botões têm ótimas técnicas aplicadas. A text-shadow é adaptável ao claro e ao escuro, criando uma aparência sutil e agradável ao botão o texto fica bem em cima do plano de fundo. Para o box-shadow, são atribuídas três sombras. A primeira, --_shadow-2, é uma sombra de caixa normal. A segunda sombra é um truque para o olho que faz o botão parecer fez um pouco de brecha. A última sombra é para o destaque ao passar o cursor, inicialmente em um tamanho 0, mas receberá um tamanho posteriormente e será transferido para que apareça cresçam a partir do botão.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  box-shadow: 
    var(--_shadow-2),
    var(--_shadow-depth),
    0 0 0 var(--_highlight-size) var(--_highlight)
  ;
  text-shadow: var(--_ink-shadow);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Layout

Dei ao botão um layout flexbox, especificamente, um layout inline-flex que se encaixará no conteúdo. Depois, centralizo do texto, e alinhe os filhos vertical e horizontalmente ao center. Isso ajudará os ícones e outros elementos do botão para alinhar corretamente.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Espaçamento

Para o espaçamento de botões, usei gap para manter irmãos de toque e lógica para padding. o espaçamento funciona para todos os layouts de texto.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  gap: 1ch;
  padding-block: var(--_padding-block);
  padding-inline: var(--_padding-inline);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

UX de toque e mouse

A próxima seção é destinada principalmente a usuários de toque em dispositivos móveis. A primeira propriedade, user-select, é para todos os usuários. impede que o texto destaque o texto do botão. Isso ocorre principalmente perceptível em dispositivos sensíveis ao toque quando um botão é tocado e pressionado e o botão o sistema destaca o texto do botão.

Acho que essa não é a experiência do usuário com botões integrados apps, então eu desativo essa opção definindo user-select como "nenhuma". Toque nas cores de destaque (-webkit-tap-highlight-color) e menus de contexto do sistema operacional (-webkit-touch-callout) há outros recursos de botões muito focados na Web, mas não alinhados com as diretrizes as expectativas do usuário, então eu também as removo.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

Transições

A variável --_transition adaptável é atribuída ao propriedade transição:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  transition: var(--_transition);
}

Ao passar o cursor, enquanto o usuário não estiver pressionando ativamente, ajustar o destaque da sombra tamanho para dar a ele uma boa aparência de foco que parece crescer de dentro botão:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
):where(:not(:active):hover) {
  --_highlight-size: .5rem;
}

Na posição em foco, aumente o deslocamento do contorno do foco do botão, oferecendo também uma aparência de foco bonita que parece aumentar de dentro do botão:

:where(button, input):where(:not(:active)):focus-visible {
  outline-offset: 5px;
}

Ícones

Para gerenciar ícones, o seletor tem um seletor :where() adicionado para SVG direto. filhos ou elementos com o atributo personalizado data-icon. O tamanho do ícone está definido com a propriedade personalizada usando propriedades lógicas inline e de bloqueio. Cor do traço e um drop-shadow para corresponder a text-shadow. flex-shrink é definido como 0 para que o ícone nunca seja apertado. Por fim, seleciono ícones com linhas e atribuo esses estilos aqui com Limites e junções de linha de fill: none e round:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
) > :where(svg, [data-icon]) {
  block-size: var(--_icon-size);
  inline-size: var(--_icon-size);
  stroke: var(--_icon-color);
  filter: drop-shadow(var(--_ink-shadow));

  flex-shrink: 0;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Personalizar botões de envio

Eu queria que os botões de envio tivessem uma aparência um pouco promovida e consegui fazendo com que a cor do texto dos botões seja a cor de destaque:

:where(
  [type="submit"], 
  form button:not([type],[disabled])
) {
  --_text: var(--_accent);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Personalizar botões de redefinição

Eu queria que os botões de redefinição tivessem sinais de aviso integrados para alertar os usuários sobre seu comportamento potencialmente destrutivo. Também optei por estilizar o tema claro com mais detalhes em vermelho do que no tema escuro. A personalização é feita pela mudar a cor clara ou escura adequada, e o botão atualize o estilo:

:where([type="reset"]) {
  --_border-light: hsl(0 100% 83%);
  --_highlight-light: hsl(0 100% 89% / 20%);
  --_text-light: hsl(0 80% 50%);
  --_text-dark: hsl(0 100% 89%);
}

Também achei que seria legal a cor do contorno do foco combinar com o destaque vermelho. A cor do texto adapta um vermelho escuro a um vermelho-claro. Eu crio a cor do contorno fazer a correspondência com a palavra-chave currentColor:

:where([type="reset"]):focus-visible {
  outline-color: currentColor;
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Personalizar botões desativados

É muito comum que botões desativados tenham baixo contraste de cor durante a tentar submeter o botão desativado para que ele pareça menos ativo. Testei cada uma e se certificou de que eles foram passados, deslocando o valor de brilho HSL até que o passada no DevTools ou no VisBug.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
)[disabled] {
  --_bg: none;
  --_text-light: hsl(210 7% 40%);
  --_text-dark: hsl(210 11% 71%);

  cursor: not-allowed;
  box-shadow: var(--_shadow-1);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Como personalizar botões de entrada de arquivo

O botão de entrada de arquivo é um contêiner para um período e um botão. O CSS é capaz de definir um pouco o estilo do contêiner de entrada e do botão aninhado, mas não o período. O contêiner recebe max-inline-size para que ele não fique maior do que necessário, enquanto inline-size: 100% se encolherá e se ajustará contêineres menores do que ele é. A cor do plano de fundo está definida como uma cor adaptável mais escura do que outras superfícies, por isso fica atrás do botão seletor de arquivos.

:where(input[type="file"]) {
  inline-size: 100%;
  max-inline-size: max-content;
  background-color: var(--_input-well);
}

O botão seletor de arquivos e os botões de tipo de entrada são fornecidos especificamente appearance: none para remover todos os estilos fornecidos pelo navegador que não foram substituído por outros estilos de botão.

:where(input[type="button"]),
:where(input[type="file"])::file-selector-button {
  appearance: none;
}

Por fim, a margem é adicionada ao inline-end do botão para enviar o texto do período. para se afastar do botão, criando espaço.

:where(input[type="file"])::file-selector-button {
  margin-inline-end: var(--_padding-inline);
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Exceções especiais ao tema escuro

Usei um fundo mais escuro para os botões de ação principais para aumentar o contraste texto, o que dá a elas uma aparência um pouco mais promovida.

@media (prefers-color-scheme: dark) {
  :where(
    [type="submit"],
    [type="reset"],
    [disabled],
    form button:not([type="button"])
  ) {
    --_bg: var(--_input-well);
  }
}

Captura de tela mostrando os botões depois da aplicação dos estilos anteriores.

Como criar variantes

Por diversão e por ser prático, escolhi mostrar como criar variantes. Uma variante é muito vibrante, semelhante a como os botões principais geralmente olhar Outra variante é grande. A última variante tem um ícone preenchido com gradiente.

Botão vibrante

Para criar esse estilo de botão, substituí os acessórios de base diretamente com azul cores. Embora tenha sido rápido e fácil, ele remove os acessórios e visuais adaptáveis o mesmo nos temas claro e escuro.

.btn-custom {
  --_bg: linear-gradient(hsl(228 94% 67%), hsl(228 81% 59%));
  --_border: hsl(228 89% 63%);
  --_text: hsl(228 89% 100%);
  --_ink-shadow: 0 1px 0 hsl(228 57% 50%);
  --_highlight: hsl(228 94% 67% / 20%);
}

O botão personalizado é mostrado em claro e escuro. Ele é azul muito vibrante, como os botões de ação principais comuns.

Botão grande

Esse estilo de botão é alcançado modificando a propriedade personalizada --_size. O padding e outros elementos de espaço são relativos a esse tamanho, dimensionando proporcionalmente ao novo tamanho.

.btn-large {
  --_size: 1.5rem;
}

O botão grande aparece ao lado do botão personalizado, cerca de 150 vezes maior.

Botão de ícone

Esse efeito de ícone não tem nada a ver com nossos estilos de botão, mas mostram como fazer isso com apenas algumas propriedades CSS e como o botão lida com ícones que não são SVG inline.

[data-icon="cloud"] {
  --icon-cloud: url("https://api.iconify.design/mdi:apple-icloud.svg") center / contain no-repeat;

  -webkit-mask: var(--icon-cloud);
  mask: var(--icon-cloud);
  background: linear-gradient(to bottom, var(--_accent-dark), var(--_accent-light));
}

Um botão com um ícone é mostrado em temas claros e escuros.

Conclusão

Agora que você sabe como eu fiz isso, o que você faria‽ 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web.

Crie uma demonstração, envie um tweet para mim e adicione os links acesse a seção "Remixes da comunidade" abaixo.

Remixes da comunidade

Não há nada aqui ainda.

Recursos