{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/componentes-nativos-em-2026-o-que-o-html-e-o-css-ja-fazem-sozinhos/","result":{"data":{"markdownRemark":{"html":"<p>Em 2014 escrevi um post aqui no blog perguntando se\n<a href=\"/blog/e-possivel-utilizar-componentes-desenvolvidos-apenas-com-css\">é possível utilizar componentes desenvolvidos apenas com CSS</a>.</p>\n<p>Na conclusão eu disse que <strong>SIM</strong> e soltei essa frase:</p>\n<blockquote>\n<p>Não duvido que isso possa ser uma tendência daqui para frente.</p>\n</blockquote>\n<p>12 anos depois voltei nesse projeto e posso dizer que passei no filtro do tempo. Tanto o combo CSS + HTML quanto o navegador evoluíram e agora conseguimos fazer tudo isso (e muito mais!) <strong>nativamente</strong>, de forma extremamente performática, acessível e com animações de brinde.</p>\n<p>Então esse post é sobre isso: o que o HTML e o CSS já fazem sozinhos em 2026, usando o projeto de 2014 como prova, componente por componente.</p>\n<h2>Um pouco de história</h2>\n<p>Tudo começou lá em 2012 com um <a href=\"https://codepen.io/felipefialho/pen/qzDCJ\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Cartman feito só com CSS</a>. Brincadeira boba, mas foi ali que comecei a buscar no CSS soluções que dessem pra usar de verdade em projetos.</p>\n<p>Em 2014 isso virou o <a href=\"https://css-components.felipefialho.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Pure CSS Components</a>: 6 componentes de interface sem nenhuma linha de JavaScript.</p>\n<ul>\n<li>Carrossel</li>\n<li>Collapse/Accordion</li>\n<li>Dropdown</li>\n<li>Modal</li>\n<li>Tab</li>\n<li>Tooltip</li>\n</ul>\n<p>Tudo feito com <code class=\"language-text\">checkbox</code> e <code class=\"language-text\">radio</code> escondidos (e no começo também com o pseudo-seletor <code class=\"language-text\">:target</code>). O estado de checado e não checado era o nosso “state management” da época.</p>\n<p>E o melhor (!!): muita gente <strong>realmente</strong> usou isso em produção em projetos reais. No GitHub ele chegou a quase 700 estrelas, o que era muita coisa pra um projeto que nasceu de uma brincadeira e da curiosidade de um dev ainda no começo de carreira explorando as possibilidades.</p>\n<p>Agora o projeto foi reconstruído do zero com uma ideia bem simples: cada componente de 2014 foi preservado exatamente como era e aparece <strong>lado a lado com a sua versão nativa de 2026</strong>. É tipo um “antes e depois” de como os navegadores e o CSS evoluíram.</p>\n<h2>Hack de 2014 vs. nativo de 2026</h2>\n<h3>Collapse</h3>\n<p>Em 2014 era um <code class=\"language-text\">&lt;input type=\"checkbox\" hidden></code> com um <code class=\"language-text\">&lt;label></code> e o seletor de irmão <code class=\"language-text\">~</code> pra mostrar o conteúdo. Se quisesse accordion, trocava pra <code class=\"language-text\">type=\"radio\"</code> com o mesmo <code class=\"language-text\">name</code>.</p>\n<p><a href=\"/blog/html-criando-um-componente-de-collapse-nativo-com-as-tags-details-e-summary\">Em 2021 já tinha falado aqui sobre os fodões <code class=\"language-text\">&lt;details></code> e <code class=\"language-text\">&lt;summary></code></a>, mas de lá pra cá eles ficaram ainda mais poderosos.</p>\n<p>Agora o accordion exclusivo (onde só um fica aberto por vez) é só colocar o mesmo <code class=\"language-text\">name</code> em todos os <code class=\"language-text\">&lt;details></code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"html\"><pre class=\"language-html\"><code class=\"language-html\"><span class=\"token comment\">&lt;!-- Same name = exclusive group: opening one closes the others --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>collapse<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>details</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-accordion<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">open</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>summary</span><span class=\"token punctuation\">></span></span>Install<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>summary</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>panel<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>Clone the repository and run <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>code</span><span class=\"token punctuation\">></span></span>pnpm install<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>code</span><span class=\"token punctuation\">></span></span>.<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>details</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>details</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-accordion<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>summary</span><span class=\"token punctuation\">></span></span>Run locally<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>summary</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>panel<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>Start the dev server with <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>code</span><span class=\"token punctuation\">></span></span>pnpm dev<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>code</span><span class=\"token punctuation\">></span></span>.<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>details</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>Percebam que é literalmente o mesmo truque do <code class=\"language-text\">radio</code> com <code class=\"language-text\">name</code> de 2014, só que agora o próprio HTML faz isso por você 😁</p>\n<p>E a animação de abrir e fechar, que sempre foi o calcanhar de Aquiles do <code class=\"language-text\">&lt;details></code>, agora dá pra fazer com o pseudo-elemento <code class=\"language-text\">::details-content</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token comment\">/* Animate the native content slot to and from height: auto */</span>\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">prefers-reduced-motion</span><span class=\"token punctuation\">:</span> no-preference<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token atrule\"><span class=\"token rule\">@supports</span> <span class=\"token punctuation\">(</span><span class=\"token property\">interpolate-size</span><span class=\"token punctuation\">:</span> allow-keywords<span class=\"token punctuation\">)</span> <span class=\"token keyword\">and</span> <span class=\"token function\">selector</span><span class=\"token punctuation\">(</span><span class=\"token selector-function-argument selector\">::details-content</span><span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token selector\">:scope</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">interpolate-size</span><span class=\"token punctuation\">:</span> allow-keywords<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token selector\">details::details-content</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">block-size</span><span class=\"token punctuation\">:</span> 0<span class=\"token punctuation\">;</span>\n      <span class=\"token property\">overflow-y</span><span class=\"token punctuation\">:</span> clip<span class=\"token punctuation\">;</span>\n      <span class=\"token property\">transition</span><span class=\"token punctuation\">:</span>\n        block-size 220ms <span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--ease<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        content-visibility 220ms allow-discrete<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token selector\">details[open]::details-content</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">block-size</span><span class=\"token punctuation\">:</span> auto<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>O <code class=\"language-text\">interpolate-size: allow-keywords</code> é o que permite animar até <code class=\"language-text\">auto</code>, coisa que a gente sonhou por anos. E percebam o <code class=\"language-text\">@supports</code>: se o navegador não conhecer o <code class=\"language-text\">::details-content</code>, o collapse continua funcionando, só que sem animação. Mesma coisa pra quem prefere menos movimento, graças ao <code class=\"language-text\">prefers-reduced-motion</code>.</p>\n<h3>Modal</h3>\n<p>No <a href=\"/blog/e-possivel-utilizar-componentes-desenvolvidos-apenas-com-css\">primeiro post de 2014</a> o modal usava o <code class=\"language-text\">:target</code>, mas a versão que ficou preservada no site é a com <code class=\"language-text\">checkbox</code> escondido e um monte de <code class=\"language-text\">&lt;label></code> apontando pra ele:</p>\n<div class=\"gatsby-highlight\" data-language=\"html\"><pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>btn<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>then-modal-one<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Modal!<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal-open<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>then-modal-one<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>checkbox<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">hidden</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal-wrap<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">aria-hidden</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">role</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>dialog<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal-overlay<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>then-modal-one<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal-dialog<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal-header<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h2</span><span class=\"token punctuation\">></span></span>Modal in CSS?<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h2</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>btn-close<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>then-modal-one<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">aria-hidden</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>×<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n      ...\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>O botão que abre é um <code class=\"language-text\">&lt;label></code>, o fundo escuro que fecha é um <code class=\"language-text\">&lt;label></code>, o ”×” é um <code class=\"language-text\">&lt;label></code>… tudo pra marcar e desmarcar um único <code class=\"language-text\">checkbox</code> 😅</p>\n<p>Funcionava, mas foco, <code class=\"language-text\">Esc</code> e leitor de tela ficavam por conta da sorte.</p>\n<p>Agora temos o <code class=\"language-text\">&lt;dialog></code> com <strong>invoker commands</strong>:</p>\n<div class=\"gatsby-highlight\" data-language=\"html\"><pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">commandfor</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-modal<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">command</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>show-modal<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  Discard changes\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>dialog</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-modal<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">closedby</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>any<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">aria-labelledby</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-modal-title<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h3</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-modal-title<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Discard unsaved changes?<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h3</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>footer</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>modal-footer<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token comment\">&lt;!-- autofocus: the safe choice gets focus when the dialog opens --></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">commandfor</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-modal<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">command</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>close<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">autofocus</span><span class=\"token punctuation\">></span></span>Keep editing<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button button-primary<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>button<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">commandfor</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>now-modal<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">command</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>close<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Discard<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>footer</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>dialog</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>O <code class=\"language-text\">commandfor</code> aponta pro <code class=\"language-text\">id</code> do <code class=\"language-text\">&lt;dialog></code> e o <code class=\"language-text\">command=\"show-modal\"</code> diz o que fazer. O <code class=\"language-text\">closedby=\"any\"</code> faz o modal fechar clicando fora ou apertando <code class=\"language-text\">Esc</code>. Foco, backdrop, top layer, tudo de graça.</p>\n<p>E pra animar usamos o <code class=\"language-text\">@starting-style</code>, que define o estado inicial do elemento no momento em que ele aparece:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">prefers-reduced-motion</span><span class=\"token punctuation\">:</span> no-preference<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">.modal,\n  .modal::backdrop</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">transition</span><span class=\"token punctuation\">:</span>\n      opacity <span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--duration<span class=\"token punctuation\">)</span> <span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--ease<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      display <span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--duration<span class=\"token punctuation\">)</span> allow-discrete<span class=\"token punctuation\">,</span>\n      overlay <span class=\"token function\">var</span><span class=\"token punctuation\">(</span>--duration<span class=\"token punctuation\">)</span> allow-discrete<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">opacity</span><span class=\"token punctuation\">:</span> 0<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token selector\">.modal[open],\n  .modal[open]::backdrop</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">opacity</span><span class=\"token punctuation\">:</span> 1<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token atrule\"><span class=\"token rule\">@starting-style</span></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token selector\">.modal[open],\n    .modal[open]::backdrop</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">opacity</span><span class=\"token punctuation\">:</span> 0<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>O <code class=\"language-text\">display</code> e o <code class=\"language-text\">overlay</code> com <code class=\"language-text\">allow-discrete</code> fazem o <code class=\"language-text\">&lt;dialog></code> continuar no top layer até o fade de saída terminar. Coisa que em 2014 a gente nem sonhava 😁</p>\n<h3>Dropdown</h3>\n<p>Em 2014 era mais um <code class=\"language-text\">checkbox</code> escondido. Agora é a <strong>Popover API</strong> com <code class=\"language-text\">popovertarget</code> e o posicionamento fica por conta do <strong>CSS anchor positioning</strong>, que prende o menu no botão sem precisar calcular nada.</p>\n<p>E a variante de hover, que antes era um <code class=\"language-text\">:hover</code> no CSS, agora usa o <code class=\"language-text\">interestfor</code>.</p>\n<h3>Tooltip</h3>\n<p>O tooltip usa <code class=\"language-text\">popover=\"hint\"</code> junto com <code class=\"language-text\">interestfor</code> e anchor positioning. É o tipo de coisa que por anos a gente instalava uma biblioteca inteira só pra fazer.</p>\n<h3>Tab</h3>\n<p>A versão nativa usa <code class=\"language-text\">&lt;details name></code> exclusivos organizados visualmente como abas.</p>\n<p>Mas aqui preciso ser honesto: sem JavaScript não dá pra implementar o padrão de tabs com navegação pelas setas do teclado. Funciona e é acessível como um grupo de <code class=\"language-text\">&lt;details></code>, mas não é o padrão completo de tabs.</p>\n<h3>Carrossel</h3>\n<p>O carrossel de 2014 era o mais “criativo” (leia-se: gambiarra). Um <code class=\"language-text\">radio</code> antes de cada item, três pares de <code class=\"language-text\">&lt;label></code> pras setas e aquele seletor monstruoso com <code class=\"language-text\">nth-child</code> pros indicadores.</p>\n<p>Agora é <code class=\"language-text\">scroll-snap</code> pra travar os itens, <code class=\"language-text\">::scroll-button()</code> pras setas e <code class=\"language-text\">::scroll-marker</code> pros indicadores. Tudo gerado pelo próprio navegador.</p>\n<h2>Nascidos nativos</h2>\n<p>Além dos 6 componentes originais, criei uma seção nova chamada <strong>Born native</strong> com 10 componentes que nem tinham versão em 2014, porque simplesmente não dava pra fazer só com CSS:</p>\n<ul>\n<li><strong>Custom select</strong> com <code class=\"language-text\">appearance: base-select</code> e <code class=\"language-text\">::picker(select)</code>. Sim, finalmente dá pra estilizar o <code class=\"language-text\">&lt;select></code> 🥳</li>\n<li><strong>Switch</strong> e <strong>segmented control</strong> usando <code class=\"language-text\">:has()</code></li>\n<li><strong>Formulários</strong> com <code class=\"language-text\">field-sizing: content</code> e <code class=\"language-text\">:user-invalid</code></li>\n<li><strong>File tree</strong> com <code class=\"language-text\">&lt;details></code> aninhados</li>\n<li><strong>Drawer de filtros</strong> com <code class=\"language-text\">&lt;dialog></code></li>\n<li><strong>Header sticky</strong> com scroll-state container queries</li>\n<li><strong>Índice com scroll-spy</strong> usando <code class=\"language-text\">scroll-target-group</code> e <code class=\"language-text\">:target-current</code></li>\n<li><strong>Barra de progresso de leitura</strong> com scroll-driven animations</li>\n<li><strong>Menu radial</strong> com <code class=\"language-text\">sibling-index()</code>, <code class=\"language-text\">sibling-count()</code>, <code class=\"language-text\">cos()</code> e <code class=\"language-text\">sin()</code></li>\n<li><strong>Galeria</strong> com cross-document view transitions</li>\n</ul>\n<p>E um detalhe que decidi manter: o botão de tema claro/escuro do site (“lights”) usa justamente o truque de 2014, um <code class=\"language-text\">checkbox</code> escondido junto com o <code class=\"language-text\">:has()</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token comment\">/* Lights toggle: a hidden checkbox flips the scheme, the 2014 way */</span>\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">prefers-color-scheme</span><span class=\"token punctuation\">:</span> light<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">:root:has(#lights:checked)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">color-scheme</span><span class=\"token punctuation\">:</span> dark<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">prefers-color-scheme</span><span class=\"token punctuation\">:</span> dark<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">:root:has(#lights:checked)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">color-scheme</span><span class=\"token punctuation\">:</span> light<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Uma homenagem pro projeto original 😁</p>\n<h2>E a performance?</h2>\n<p>A grande questão de usar esse tipo de componente é <strong>performance</strong>. Então rodei o Lighthouse na home do site (build de produção, preview local):</p>\n<ul>\n<li><strong>Mobile:</strong> Performance 90, LCP 2,9 s, Total Blocking Time 0 ms e CLS 0</li>\n<li><strong>Desktop:</strong> Performance 100, LCP 0,6 s, Total Blocking Time 0 ms e CLS 0,008</li>\n<li><strong>Nos dois:</strong> Accessibility 97, Best Practices 100 e SEO 100</li>\n</ul>\n<p>E o que vai pro navegador:</p>\n<ul>\n<li>JavaScript: <strong>0 arquivos, 0 KB</strong></li>\n<li>CSS: dois arquivos, uns 2,3 KB e 1,4 KB com gzip</li>\n<li>Peso total da página: 301 KiB, a maior parte fontes self-hosted e imagens</li>\n</ul>\n<p>Pra comparar: só o jQuery 3.7.1 tem 87 KB minificado (uns 30 KB com gzip), e isso antes de qualquer plugin. E a stack típica de 2014 era exatamente essa, jQuery + um plugin pra cada componente.</p>\n<p>Mas o ganho não é só de peso. Quando o navegador faz o trabalho:</p>\n<ul>\n<li>Não tem JavaScript pra baixar, fazer parse e executar</li>\n<li>Nada roda na main thread pra controlar estado, abrir e fechar é com o próprio navegador</li>\n<li><code class=\"language-text\">&lt;dialog></code> e popover vivem no top layer, então acabou a guerra de <code class=\"language-text\">z-index</code></li>\n<li>Scroll-driven animations e scroll snapping podem rodar fora da main thread</li>\n<li>Não tem hidratação</li>\n<li>Funciona com o JavaScript desabilitado (e sim, testei)</li>\n<li>Menos código significa menos lugar pra bug se esconder</li>\n<li>Acessibilidade vem de fábrica: foco, <code class=\"language-text\">Esc</code> e light dismiss</li>\n</ul>\n<p>Por isso também decidi tirar o Google Tag Manager e os anúncios que o site tinha antes. O site não tem analytics, não tem nada te rastreando, e foi isso que deixou ele <strong>zero JavaScript de verdade</strong>. Se quiser apoiar o projeto, tem o <a href=\"https://github.com/sponsors/felipefialho\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">GitHub Sponsors</a>.</p>\n<p>Agora, sendo honesto: o HTML da home é grandão, uns 1,37 MB cru (76 KB com gzip). Isso porque o código de cada demo já vem com syntax highlight e fica embutido na página, mesmo com os painéis de “ver código” fechados. O 90 no mobile vem basicamente da primeira pintura de uma página muito longa, não de interatividade. Tá na lista de melhorias, tipo carregar esse código só quando alguém pedir.</p>\n<h2>Nem tudo são flores</h2>\n<p>Vários desses recursos hoje só funcionam em navegadores baseados em Chromium.</p>\n<p>Por isso o site lista o suporte de cada funcionalidade e <strong>todo componente tem um fallback utilizável</strong>. Se o navegador não suportar, você não fica com um componente quebrado, só com uma versão mais simples.</p>\n<p>Isso não é um “pode usar tudo em produção amanhã”. É um retrato de pra onde os navegadores tão indo.</p>\n<h2>Zero JavaScript, mas 100% vibe coding</h2>\n<p>Tem uma ironia bem divertida nessa história toda:</p>\n<blockquote>\n<p>um projeto que tem como regra <strong>não ter nenhuma linha de JavaScript</strong> só voltou à vida porque eu fiz totalmente no vibe coding</p>\n</blockquote>\n<p>Mas antes preciso fazer uma confissão: eu enrolei <strong>anos (décadas!)</strong> pra mexer nesse projeto hahaha</p>\n<p>Software abandonado não fica parado, ele apodrece. Passei quase 10 anos sem fazer nenhuma grande melhoria e, quando finalmente fui olhar com calma, encontrei imagens de placeholder mortas, CDN do rawgit que não existe mais, instruções de instalação via Bower e 39 vulnerabilidades no <code class=\"language-text\">npm audit</code>, sendo 1 crítica. Aquela sensação de abrir uma gaveta que você não mexe faz 10 anos.</p>\n<p>Não era só atualizar dependência, era refazer o projeto inteiro, e isso na minha cabeça era coisa de semanas de trabalho que nunca cabiam na minha vida de agora. A semana que vem durou bons anos 😅</p>\n<p>Em 2026 fazer esse tipo de projeto é praticamente trivial usando LLMs. Então finalmente refiz <strong>o projeto inteiro, design incluído, em algumas horas</strong> usando meu fiel escudeiro Claudinho Code, naquele rolê de planning, skills, roles e subagents que já abordei em n posts <a href=\"/blog/stack-com-ai-que-vai-elevar-seu-trampo-como-dev\">aqui</a> e nas redes por aí.</p>\n<p>E quero ser 100% transparente aqui: <strong>eu não escrevi nenhuma linha de código desse projeto na mão</strong>. Nem HTML, nem CSS, nem config. Tudo saiu de conversa com o <a href=\"https://claude.ai/code\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Claude Code</a>, numa sessão longa de algumas horas. Meu trabalho foi direcionar, revisar e decidir.</p>\n<h3>Como foi na prática</h3>\n<p>Começou no modo plan: análise da stack antiga e um plano escrito que eu li, ajustei e aprovei antes de qualquer linha de código. Nada de sair vibe codando no escuro.</p>\n<p>Depois veio a execução em commits atômicos e PRs, com subagents em paralelo: Opus pra design, pesquisa e partes mais complexas, Sonnet pras tarefas mais mecânicas.</p>\n<p>Pra decidir quais componentes “Born native” dava pra fazer sem JavaScript, um subagent usou o MCP do octocode pra vasculhar MDN, web-features e specs, e conferiu o suporte real dos navegadores no <a href=\"https://webstatus.dev\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">webstatus.dev</a>. Isso é <a href=\"/blog/ai-e-engenharia-de-contexto-em-um-projeto-do-zero-com-vibe-coding\">engenharia de contexto</a> na prática: a LLM não chuta, ela vai atrás da fonte.</p>\n<p>E cada mudança foi testada num navegador de verdade com Playwright: modo claro e escuro, desktop e mobile, teclado e mouse, e até uma rodada com o JavaScript desabilitado pra provar que o zero JS era real. Somando isso com lint, type-check, build e agents de self-review antes de cada PR.</p>\n<h3>A AI errou (e também pegou erro)</h3>\n<p>Nem tudo foi lindo, e é justamente por isso que esse processo todo existe:</p>\n<ul>\n<li>O CSS de 2014 portado saiu <strong>60% maior</strong>, porque os <code class=\"language-text\">rem</code> eram baseados no velho <code class=\"language-text\">font-size: 62.5%</code> na raiz. Uma rodada de review pegou.</li>\n<li>Um agent cravou uma versão de suporte de navegador que o webstatus.dev provou estar errada</li>\n<li>A cor dos links falhou no teste de contraste e teve que ser corrigida</li>\n</ul>\n<p>Nada de outro mundo, dev humano faria merdas muito maiores. A diferença é ter um processo que pega antes de ir pra produção, e ficar de olho continua sendo trabalho meu.</p>\n<p>E teve uma que ninguém lembrava: o deploy de produção no Netlify tava <strong>travado desde outubro de 2023</strong>, com a publicação automática desligada. Ou seja, nada ia pro ar fazia anos. O Claude achou isso e destravou pelo CLI/MCP do Netlify. De quebra, as 7 URLs antigas de download de zip agora redirecionam pra uma release no GitHub, pra ninguém cair num link quebrado.</p>\n<h3>O que foi decisão minha</h3>\n<p>No fim quem continua decidindo sou eu, então:</p>\n<ul>\n<li>Astro + pnpm + CSS puro (cheguei a considerar SCSS, mas CSS puro em 2026 dá conta de tudo)</li>\n<li>Manter os componentes de 2014 intactos</li>\n<li>A ideia do “Then vs Now” lado a lado</li>\n<li>Tipografia em minúsculo</li>\n<li>O favicon com emoji de quebra-cabeça 🧩</li>\n<li>Tirar Google Tag Manager e qualquer linha de JS</li>\n<li>Pedir este post e a capa, que também foi gerada na mesma sessão (o texto eu reescrevi quase tudo, claro 😅)</li>\n</ul>\n<p>Ou seja, meu trampo aqui <strong>foi exatamente o que se espera de um software engineer em 2026</strong>: sustentar o próprio ecossistema de AI (skills, rules, MCPs e subagents bem configurados), planejar e aprovar o plano antes de qualquer código, decidir arquitetura e stack, revisar cada output desconfiando de tudo que não foi verificado e dizer não quando a sugestão não faz sentido.</p>\n<p>Como falei no post sobre <a href=\"/blog/desenvolvimento-front-end-e-programacao-em-2026-o-que-esperar-do-futuro\">o que esperar de 2026</a>, não vamos mais escrever código na unha, mas cada vez mais orientar, revisar e orquestrar. E como sempre digo, independentemente do ferramental que você usa, o responsável por cada entrega segue sendo VOCÊ.</p>\n<h2>Conclusão</h2>\n<p>Em 2014 a gente usava <code class=\"language-text\">checkbox</code> escondido porque era o único jeito. Em 2026 o HTML e o CSS fazem isso nativamente, com acessibilidade, foco e animação de brinde, e sem mandar 1 KB de JavaScript pro usuário.</p>\n<p>Aquela “tendência” que eu previ aconteceu, só que o navegador chegou lá antes de mim.</p>\n<p>E depois de anos adiando, bastaram algumas horas com uma AI do lado pra esse projeto voltar à vida. Fica a lição pra aquele side project que tá largado aí na sua gaveta também 😉</p>\n<ul>\n<li><a href=\"https://css-components.felipefialho.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Pure CSS Components</a></li>\n<li><a href=\"https://github.com/felipefialho/css-components\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Código no GitHub</a></li>\n<li><a href=\"/blog/e-possivel-utilizar-componentes-desenvolvidos-apenas-com-css\">O post original de 2014</a></li>\n</ul>","fields":{"slug":"blog/componentes-nativos-em-2026-o-que-o-html-e-o-css-ja-fazem-sozinhos/"},"frontmatter":{"date":"28 de setembro de 2026","description":"Modal, dropdown, tooltip, carrossel e muito mais sem nenhuma linha de JavaScript, lado a lado com as gambiarras de 2014 😁","title":"Componentes nativos em 2026: o que o HTML e o CSS já fazem sozinhos","tags":["css","html","componentes","front-end","performance"],"image":{"id":"708a3064-dbea-5c6f-9c39-e87590151c42","publicURL":"/static/e2494d84dc6964d85ac08d881a0c2652/2026-09-28-cover.jpg","childImageSharp":{"fluid":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAIDBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB412VUkQ//8QAGBAAAwEBAAAAAAAAAAAAAAAAAAECERD/2gAIAQEAAQUClIpThpvP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFxAAAwEAAAAAAAAAAAAAAAAAACAhMf/aAAgBAQAGPwKmL//EABkQAQEBAAMAAAAAAAAAAAAAAAEAMREhof/aAAgBAQABPyHViyvUncKU3N//2gAMAwEAAgADAAAAEAPf/8QAFhEBAQEAAAAAAAAAAAAAAAAAEQEQ/9oACAEDAQE/ECuf/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EBMn/8QAGRABAAMBAQAAAAAAAAAAAAAAAQAhMRFB/9oACAEBAAE/EOwtGg3Al7LmKglMhPYh1dip/9k=","aspectRatio":1.6666666666666667,"src":"/static/e2494d84dc6964d85ac08d881a0c2652/ce4b6/2026-09-28-cover.jpg","srcSet":"/static/e2494d84dc6964d85ac08d881a0c2652/1983d/2026-09-28-cover.jpg 320w,\n/static/e2494d84dc6964d85ac08d881a0c2652/994cc/2026-09-28-cover.jpg 640w,\n/static/e2494d84dc6964d85ac08d881a0c2652/ce4b6/2026-09-28-cover.jpg 1280w,\n/static/e2494d84dc6964d85ac08d881a0c2652/0dcb7/2026-09-28-cover.jpg 1800w","sizes":"(max-width: 1280px) 100vw, 1280px"}}}},"timeToRead":13}},"pageContext":{"slug":"blog/componentes-nativos-em-2026-o-que-o-html-e-o-css-ja-fazem-sozinhos/","previous":{"fields":{"slug":"blog/stack-com-ai-que-vai-elevar-seu-trampo-como-dev/"},"frontmatter":{"title":"Stack com AI que vai elevar seu trampo como dev","date":"05 fev, 2026"}},"next":null}},"staticQueryHashes":["2228468640","2451431863","469472723","63159454"]}