<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Carolina Gonçalves</title>
    <description>The latest articles on DEV Community by Carolina Gonçalves (@carol8fml).</description>
    <link>https://dev.to/carol8fml</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3415084%2F8350186a-444a-4f1f-9eaa-74ad7ed23879.jpg</url>
      <title>DEV Community: Carolina Gonçalves</title>
      <link>https://dev.to/carol8fml</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carol8fml"/>
    <language>en</language>
    <item>
      <title>Construindo um hub de notificações desacoplado com NestJS, Nx e Design Patterns</title>
      <dc:creator>Carolina Gonçalves</dc:creator>
      <pubDate>Tue, 03 Feb 2026 14:52:27 +0000</pubDate>
      <link>https://dev.to/carol8fml/construindo-um-hub-de-notificacoes-desacoplado-com-nestjs-nx-e-design-patterns-44m2</link>
      <guid>https://dev.to/carol8fml/construindo-um-hub-de-notificacoes-desacoplado-com-nestjs-nx-e-design-patterns-44m2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota para desenvolvedores:&lt;/strong&gt; Se você prefere ir direto ao código, o repositório completo com a implementação desta arquitetura está disponível aqui: &lt;a href="https://github.com/carol8fml/nexus-notification-hub" rel="noopener noreferrer"&gt;github.com/carol8fml/nexus-notification-hub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Olá, pessoal! 🤟🏾&lt;/p&gt;

&lt;p&gt;Há alguns anos, recebi a tarefa de avaliar serviços externos para envio transacional de e-mails. Na época, já utilizávamos o SendGrid em quatro aplicações web e surgiu a necessidade de expandir essa solução para o aplicativo principal da empresa.&lt;/p&gt;

&lt;p&gt;A previsão era um acréscimo de &lt;strong&gt;250.000 envios por mês&lt;/strong&gt;. A partir disso, analisei mais de 10 provedores e encontrei alternativas equivalentes e mais baratas. No entanto, o verdadeiro custo só apareceu no final: &lt;strong&gt;a mão de obra dos desenvolvedores&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Substituir o serviço nas aplicações já integradas ao SendGrid exigiria um esforço enorme, porque a lógica estava fortemente acoplada à implementação do provedor, tanto nos projetos com abordagem funcional quanto naqueles estruturados com princípios próximos da Clean Architecture.&lt;/p&gt;

&lt;p&gt;Depois de alinhar com o time, ficou claro que essa mudança levaria &lt;strong&gt;meses&lt;/strong&gt; para entrar nas Sprints, já que seria necessário deslocar desenvolvedores das entregas principais exclusivamente para refatoração.&lt;/p&gt;

&lt;p&gt;No fim, continuamos com a ferramenta, mesmo não sendo a opção mais econômica. Na prática, a empresa acabou ficando refém do serviço por uma limitação técnica.&lt;/p&gt;

&lt;p&gt;Recentemente, durante meus estudos em Arquitetura de Software, decidi ir além do consumo de teoria e aplicar esse conhecimento para resolver problemas reais que enfrentei ao longo da carreira. Foi dessa experiência que surgiu o &lt;strong&gt;Nexus Notification Hub&lt;/strong&gt;: uma POC criada para validar uma arquitetura orientada ao baixo acoplamento, utilizando uma stack robusta (NestJS, Nx) e padrões clássicos, principalmente o &lt;strong&gt;Adapter Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se tivéssemos essa estrutura na época, o cenário seria outro:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Criar apenas um novo arquivo (o Adapter).&lt;/li&gt;
&lt;li&gt;Alterar uma linha de configuração.&lt;/li&gt;
&lt;li&gt;Resolver o problema.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A seguir, mostro como implementei essa solução na prática, permitindo trocar o SendGrid pela AWS ou qualquer outro provedor em minutos, e não meses.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Arquitetura: Adapter Pattern na Prática
&lt;/h2&gt;

&lt;p&gt;A solução se baseia em dois padrões clássicos trabalhando em conjunto: o &lt;strong&gt;Adapter Pattern&lt;/strong&gt; e o &lt;strong&gt;Factory Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O Adapter foi utilizado para impedir que a regra de negócio conhecesse detalhes de provedores externos. Já o Factory centraliza a escolha do provedor e evita condicionais espalhadas pela aplicação.&lt;/p&gt;

&lt;p&gt;A ideia é ter uma camada que impede a lógica de negócio de depender de implementações específicas de serviços de e-mail.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. O Contrato: A Interface &lt;code&gt;NotificationProvider&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Tudo começa com uma interface que define o contrato mínimo que qualquer provedor de notificação precisa seguir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa interface define o contrato central da arquitetura. Independentemente de estarmos usando SendGrid, AWS SES, Mailtrap ou qualquer outro serviço, todos precisam implementar o método &lt;code&gt;send&lt;/code&gt; com essa assinatura.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Na prática:&lt;/strong&gt; O restante do código (Controller, Factory, Use Cases) não precisa saber qual serviço está sendo utilizado. Ele trabalha apenas com esse contrato.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. A Implementação: &lt;code&gt;MailtrapProvider&lt;/code&gt; como Adapter
&lt;/h3&gt;

&lt;p&gt;Cada serviço de e-mail possui sua própria forma de integração (API REST, SDK proprietário, SMTP, etc.). O Adapter Pattern resolve isso criando uma tradução entre a interface comum e a implementação específica.&lt;/p&gt;

&lt;p&gt;Veja como ficou o adapter do Mailtrap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MailtrapProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Transporter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MAILTRAP_HOST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MAILTRAP_PORT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MAILTRAP_USER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MAILTRAP_PASS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noreply@nexus.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notification from Nexus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Toda a complexidade do &lt;code&gt;nodemailer&lt;/code&gt; e da configuração do Mailtrap permanece encapsulada nessa implementação. Se surgir a necessidade de trocar para SendGrid, basta criar um &lt;code&gt;SendGridProvider&lt;/code&gt; implementando a mesma interface. O restante do sistema continua funcionando sem alterações.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. O Factory: Centralizando a Decisão
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;Factory Pattern&lt;/strong&gt; centraliza a lógica de escolha do provedor, evitando &lt;code&gt;if/else&lt;/code&gt; espalhados pelo código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailtrapProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MailtrapProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;getProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailtrapProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unsupported notification type: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Factory recebe os providers via Injeção de Dependência do NestJS. Para adicionar um novo provider, basta:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Criar a classe do Adapter.&lt;/li&gt;
&lt;li&gt;Injetar no Factory.&lt;/li&gt;
&lt;li&gt;Adicionar um &lt;code&gt;case&lt;/code&gt; no switch.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  4. O Controller: Trabalhando com Abstrações
&lt;/h3&gt;

&lt;p&gt;No Controller, o desacoplamento fica evidente. Ele não precisa conhecer Mailtrap, SendGrid ou qualquer outro serviço:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api/notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationsController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;notificationFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NotificationFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendNotificationDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notificationFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Notification sent via &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota arquitetural:&lt;/strong&gt; Neste exemplo didático, o cliente define o tipo de notificação via DTO. Em um cenário real, essa decisão poderia ser tomada automaticamente pelo backend (considerando regras de failover, custo ou disponibilidade), mantendo o cliente agnóstico quanto ao provedor.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. A Cola: Injeção de Dependência (NestJS)
&lt;/h3&gt;

&lt;p&gt;O NestJS gerencia o ciclo de vida e a injeção dessas classes através do módulo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;controllers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;NotificationsController&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;NotificationFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MailtrapProvider&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationsModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Vendo na Prática
&lt;/h2&gt;

&lt;p&gt;Abaixo, uma demonstração do fluxo completo: o frontend (React) enviando uma requisição para o backend (NestJS), que utiliza o Adapter do Mailtrap para realizar o envio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/user-attachments/assets/5f9febbf-4db6-492e-aa2b-e81e4434f095" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnz9x2sgu1swkzo1ndfl4.png" alt="Demonstração do envio de email: Clique para assistir ao vídeo em alta resolução" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Escalabilidade na Prática
&lt;/h2&gt;

&lt;p&gt;Supondo que seja necessário adicionar o &lt;strong&gt;SendGrid&lt;/strong&gt;, quantos arquivos precisam ser alterados?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Criar o Adapter&lt;/strong&gt; (&lt;code&gt;sendgrid.provider.ts&lt;/code&gt;) implementando &lt;code&gt;NotificationProvider&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atualizar o Factory&lt;/strong&gt;, adicionando o novo provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atualizar o Módulo&lt;/strong&gt;, registrando o provider.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Resultado:&lt;/strong&gt;&lt;br&gt;
O Controller permanece intacto.&lt;br&gt;
A lógica de negócio permanece intacta.&lt;br&gt;
Os testes existentes continuam válidos.&lt;/p&gt;


&lt;h3&gt;
  
  
  Bônus: DTOs Compartilhados (Nx Monorepo)
&lt;/h3&gt;

&lt;p&gt;Como estamos utilizando &lt;strong&gt;Nx&lt;/strong&gt;, aproveitei para compartilhar os DTOs entre Frontend e Backend, garantindo consistência contratual entre as aplicações.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendNotificationDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsEnum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNotEmpty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// ... outros campos&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o contrato for alterado, o build do Frontend e do Backend falha imediatamente, evitando inconsistências silenciosas de integração.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão: A Liberdade da Abstração
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;Nexus Notification Hub&lt;/strong&gt; surgiu como uma resposta técnica para um impasse de negócio que vivi no passado. Um problema que antes exigiria meses de refatoração passa a ser resolvido em poucas horas.&lt;/p&gt;

&lt;p&gt;Mais do que aplicar padrões ou frameworks, a principal lição foi perceber como decisões arquiteturais influenciam diretamente a capacidade de adaptação de um sistema.&lt;/p&gt;

&lt;p&gt;Naquela época, ficamos reféns do SendGrid não porque ele era insubstituível, mas porque o código estava estruturado dessa forma. Com uma arquitetura baseada em abstrações, a decisão volta para as mãos do time e do negócio.&lt;/p&gt;

&lt;p&gt;Por outro lado, esse tipo de abordagem também adiciona complexidade e só faz sentido quando existe uma possibilidade real de troca de fornecedor ou crescimento do sistema. Caso contrário, existe o risco de adicionar complexidade sem trazer ganho real para o sistema.&lt;/p&gt;

&lt;p&gt;Se hoje surgisse novamente a necessidade de escalar para 250.000 envios mensais trocando de fornecedor, a resposta provavelmente não seria mais "não dá". Seria algo próximo de:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Ok, me dá uma tarde para implementar o novo Adapter."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hoje entendo que essa diferença está menos ligada a escrever código e mais a construir soluções que conseguem evoluir com o tempo.&lt;/p&gt;




&lt;h3&gt;
  
  
  Código Fonte
&lt;/h3&gt;

&lt;p&gt;O projeto completo, incluindo a configuração do Monorepo Nx, o frontend em React e todos os testes, está disponível no GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/carol8fml/nexus-notification-hub" rel="noopener noreferrer"&gt;github.com/carol8fml/nexus-notification-hub&lt;/a&gt;. 🤟🏾&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>nx</category>
      <category>designpatterns</category>
      <category>nestjs</category>
    </item>
    <item>
      <title>Como criar um menu hambúrguer acessível com a "armadilha de foco" (Focus Trap)</title>
      <dc:creator>Carolina Gonçalves</dc:creator>
      <pubDate>Sun, 12 Oct 2025 15:50:39 +0000</pubDate>
      <link>https://dev.to/carol8fml/como-criar-um-menu-hamburguer-acessivel-com-a-armadilha-de-foco-focus-trap-2l5i</link>
      <guid>https://dev.to/carol8fml/como-criar-um-menu-hamburguer-acessivel-com-a-armadilha-de-foco-focus-trap-2l5i</guid>
      <description>&lt;h4&gt;
  
  
  Olá, pessoal!
&lt;/h4&gt;

&lt;p&gt;Vamos direto ao ponto: seu &lt;strong&gt;menu hambúrguer provavelmente está quebrado para usuários de leitores de tela&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Eu descobri isso da pior maneira. Em 2022, enquanto desenvolvia o site de um evento sobre acessibilidade digital, o designer responsável me fez a seguinte pergunta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Você já testou o layout com leitor de tela? O evento é sobre acessibilidade, precisamos dar o exemplo...”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fui testar no mobile e encontrei o problema: o leitor de tela lia todos os links de um menu visualmente fechado. A pessoa ouvia “Contato, link”, pressionava o "Enter" e nada acontecia. Era um &lt;strong&gt;link fantasma&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tomei aquele "puxão de orelha" e aprendi uma lição: &lt;strong&gt;a experiência do usuário tem que ser a mesma para todos&lt;/strong&gt;, esconder um menu só com CSS não resolve o problema. Era preciso gerenciar o estado (aberto/fechado) e, principalmente, o foco do teclado.&lt;/p&gt;

&lt;p&gt;E a solução que encontrei foi implementar a &lt;strong&gt;"Focus Trap"&lt;/strong&gt; (armadilha de foco) usando JavaScript e atributos ARIA. Vou te mostrar o passo a passo:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Para quem já quiser ver na prática, acesse o &lt;a href="https://d9m92w.csb.app" rel="noopener noreferrer"&gt;resultado final&lt;/a&gt; e o projeto completo no &lt;a href="https://codesandbox.io/p/sandbox/accessible-menu-with-focus-trap-forked-d9m92w" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  A estrutura HTML do menu
&lt;/h3&gt;

&lt;p&gt;Tudo começa com um HTML semântico dividido em duas partes: o botão que dispara a ação e o painel que guarda os links.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. O botão de ativação&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;É onde o usuário clica para abrir o menu. E a dúvida mais comum nessa parte é: &lt;strong&gt;por que o leitor de tela precisa ouvir “botão” e não “link”?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pense assim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Um &lt;strong&gt;link&lt;/strong&gt; é como uma porta: leva o usuário pra outro lugar.&lt;/li&gt;
&lt;li&gt;Um &lt;strong&gt;botão&lt;/strong&gt; é como um interruptor: executa uma ação onde ele já está.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se o leitor de tela anuncia “Menu, link”, a experiência fica confusa. Por isso usamos &lt;code&gt;role="button"&lt;/code&gt;, que comunica ao leitor de tela que o elemento executa uma ação local.&lt;/p&gt;

&lt;p&gt;Agora, talvez você se pergunte: &lt;strong&gt;por que usar &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; e não &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; de vez?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A resposta é simples: a tag &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; com &lt;code&gt;href&lt;/code&gt; serve como &lt;strong&gt;plano B&lt;/strong&gt;. Se o JavaScript falhar (erro, conexão ruim, bloqueio por extensão), o &lt;code&gt;href&lt;/code&gt; ainda leva o usuário até a seção do menu. Um &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; puro não funcionaria.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; 
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"pageHeaderHamburgerIcon"&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#pageContainerMainNavMobile"&lt;/span&gt;
  &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;aria-controls=&lt;/span&gt;&lt;span class="s"&gt;"navigation"&lt;/span&gt;
  &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Abrir menu de navegação"&lt;/span&gt;
  &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Abrir menu de navegação"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ☰&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Abrir menu&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resumindo a função de cada um:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;role="button"&lt;/code&gt;&lt;/strong&gt;: Ajusta a expectativa do leitor de tela, anunciando “botão”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;href&lt;/code&gt;&lt;/strong&gt;: Garante o fallback se o JavaScript falhar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;: Identifica o botão para que o JavaScript possa manipulá-lo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-controls&lt;/code&gt;&lt;/strong&gt;: Conecta o botão ao menu que ele controla.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-expanded&lt;/code&gt;&lt;/strong&gt;: Indica o estado atual, &lt;code&gt;false&lt;/code&gt; (fechado) ou &lt;code&gt;true&lt;/code&gt; (aberto).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-label&lt;/code&gt; e &lt;code&gt;title&lt;/code&gt;&lt;/strong&gt;:  Garantem que a função do botão seja sempre clara. Quando o menu está aberto, o JavaScript atualiza o texto para "Fechar menu", e vice-versa, mantendo o usuário sempre informado sobre a próxima ação.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. O painel de navegação&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;É o bloco que guarda os links e que aparece e desaparece.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"pageContainerMainNavMobile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"mainNavigationLabelMobile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Menu Principal&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"mainNavigationMobile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Saiba mais sobre o festival"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sobre&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Veja quem realiza o evento"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Realização&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Entre em contato conosco"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Contato&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;O essencial aqui:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; com &lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;: A tag &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; é a correta para um bloco de links principal. O &lt;code&gt;id&lt;/code&gt; é o "alvo" do &lt;code&gt;aria-controls&lt;/code&gt; do botão.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;h2 class="sr-only"&amp;gt;&lt;/code&gt;&lt;/strong&gt;: Este título invisível é uma prática recomendada, pois o leitor de tela o utiliza para dizer ao usuário onde ele está antes de listar as opções de navegação.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Onde a mágica acontece: o JavaScript
&lt;/h3&gt;

&lt;p&gt;O JavaScript é o nosso mestre da bateria: ele dá o ritmo, controla o abre/fecha, atualiza os atributos ARIA e, o mais importante, gerencia o foco do teclado. &lt;/p&gt;

&lt;p&gt;Vamos ver a lógica passo a passo:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Preparando o terreno&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Antes de qualquer ação, nosso script precisa "conhecer" os elementos do HTML. Por isso, começamos selecionando o botão, o menu e o último link em variáveis para usarmos depois.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;menuButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageHeaderHamburgerIcon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageContainerMainNavMobile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;menuLinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;navMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;menuLinks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;menuLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;2. As funções &lt;code&gt;openMenu&lt;/code&gt; e &lt;code&gt;closeMenu&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Estas são as duas funções que controlam o estado do menu, atualizando os atributos e controlando a visibilidade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Atualiza os atributos para o estado "Aberto"&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-expanded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fechar menu de navegação&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fechar menu de navegação&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Troca o ícone e mostra o menu&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u00D7&amp;lt;span class="sr-only"&amp;gt;Fechar menu&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;navMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;closeMenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Atualiza os atributos para o estado "Fechado"&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-expanded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Abrir menu de navegação&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Abrir menu de navegação&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Troca o ícone e esconde o menu&lt;/span&gt;
    &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u2630&amp;lt;span class="sr-only"&amp;gt;Abrir menu&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;navMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;3. O gatilho e a armadilha de foco&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Agora conectamos as funções aos eventos do usuário (clique e teclado) para controlar o foco.&lt;/p&gt;

&lt;p&gt;Primeiro, fazemos o menu fechar automaticamente ao clicar em um link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Fecha o menu ao clicar em um dos seus links&lt;/span&gt;
  &lt;span class="nx"&gt;menuLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;closeMenu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em seguida, configuramos o gatilho do menu e a navegação por teclado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Gatilho do clique no botão&lt;/span&gt;
  &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isExpanded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-expanded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;isExpanded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;closeMenu&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;openMenu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Tecla ESC fecha o menu&lt;/span&gt;
  &lt;span class="nx"&gt;navMenu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;closeMenu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Tab no último item volta para o botão (o loop do foco)&lt;/span&gt;
  &lt;span class="nx"&gt;lastLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Shift+Tab no botão (com menu aberto) vai para o último item&lt;/span&gt;
  &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isExpanded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;menuButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-expanded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isExpanded&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;lastLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Resultado
&lt;/h3&gt;

&lt;p&gt;O menu agora é funcional para todos, visível e navegável por teclado. Teste com leitores de tela (NVDA, VoiceOver) e navegação por teclado (Tab, Shift+Tab, Esc).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h24g3ke1baa4tkznvfe.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h24g3ke1baa4tkznvfe.gif" alt="GIF animado mostrando a navegação por teclado em um menu mobile. O foco do teclado circula entre o botão de menu e os links internos, sem escapar para o conteúdo da página, demonstrando a 'armadilha de foco'." width="778" height="346"&gt;&lt;/a&gt; &lt;em&gt;Demonstração do menu acessível com a 'Armadilha de Foco' em funcionamento.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resultado final:&lt;/strong&gt; &lt;a href="https://d9m92w.csb.app" rel="noopener noreferrer"&gt;Acesse a demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projeto completo:&lt;/strong&gt; &lt;a href="https://codesandbox.io/p/sandbox/accessible-menu-with-focus-trap-forked-d9m92w" rel="noopener noreferrer"&gt;Acesse o código no CodeSandbox&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Conclusão
&lt;/h3&gt;

&lt;p&gt;Uma coisa que eu sempre falo: &lt;strong&gt;só podemos dizer que um site seguiu as boas práticas quando ele é acessível ao maior número de pessoas possível&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Da próxima vez que você for codificar um menu, um modal ou um simples botão, faça aquela pergunta: "Eu testei com leitor de tela?".&lt;/p&gt;

&lt;p&gt;A diferença na experiência do usuário é enorme.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>a11y</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Como defer me ajudou a equilibrar performance e acessibilidade</title>
      <dc:creator>Carolina Gonçalves</dc:creator>
      <pubDate>Thu, 07 Aug 2025 20:19:43 +0000</pubDate>
      <link>https://dev.to/carol8fml/como-defer-me-ajudou-a-equilibrar-performance-e-acessibilidade-1k9e</link>
      <guid>https://dev.to/carol8fml/como-defer-me-ajudou-a-equilibrar-performance-e-acessibilidade-1k9e</guid>
      <description>&lt;h2&gt;
  
  
  Olá, pessoal!
&lt;/h2&gt;

&lt;p&gt;Quero compartilhar uma solução simples que resolveu um dilema comum: &lt;strong&gt;como garantir acessibilidade sem prejudicar a performance?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Na empresa onde trabalho, é comercializado um plugin de acessibilidade. E como é padrão no desenvolvimento web, o script dele era inserido no final da página dos clientes, o famoso &lt;code&gt;joga no final do &amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mas aí surgiu um problema.&lt;/p&gt;

&lt;p&gt;Usuários de leitores de tela precisavam percorrer &lt;strong&gt;todo o conteúdo da página&lt;/strong&gt; até finalmente encontrarem o plugin. E como esses leitores seguem a ordem do DOM, o plugin só era lido &lt;strong&gt;lá no fim da navegação&lt;/strong&gt;, o que tornava a experiência cansativa.&lt;/p&gt;

&lt;p&gt;Alguns clientes, tentando melhorar a acessibilidade, moveram o script para o &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; do HTML. Isso de fato resolveu a ordem de leitura, mas acabou gerando outro problema: &lt;strong&gt;o site passou a demorar mais para carregar&lt;/strong&gt;, já que o script bloqueava o carregamento da página.&lt;/p&gt;

&lt;p&gt;Foi aí que, depois de alguns testes, encontrei uma solução simples e eficaz que pode ser útil para vários cenários parecidos: o &lt;strong&gt;atributo &lt;code&gt;defer&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  O que é o &lt;code&gt;defer&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; é um atributo HTML que você coloca na tag &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; para otimizar o desempenho do carregamento da página.&lt;/p&gt;

&lt;p&gt;Na prática, ele diz ao navegador: &lt;strong&gt;"Pode baixar esse script agora, mas só executa depois que o HTML estiver todo carregado."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ou seja:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O script é &lt;strong&gt;baixado em paralelo&lt;/strong&gt; com o HTML;&lt;/li&gt;
&lt;li&gt;Mas só é executado &lt;strong&gt;no final&lt;/strong&gt;, quando o DOM já estiver pronto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim, você pode colocar seus scripts no &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; sem se preocupar com bloqueios ou com a ordem de carregamento do DOM.&lt;/p&gt;




&lt;h3&gt;
  
  
  Sem &lt;code&gt;defer&lt;/code&gt;, o que acontece?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se estiver no &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, o navegador &lt;strong&gt;pausa o carregamento do HTML&lt;/strong&gt; para baixar e executar o script. Isso pode prejudicar a performance, especialmente em conexões mais lentas, e causar a sensação de que o site está travando.&lt;/p&gt;




&lt;h3&gt;
  
  
  Com &lt;code&gt;defer&lt;/code&gt;, o fluxo muda:
&lt;/h3&gt;

&lt;p&gt;1.  O HTML carrega normalmente;&lt;br&gt;
2.  O script é baixado &lt;strong&gt;ao mesmo tempo&lt;/strong&gt;, em segundo plano;&lt;br&gt;
3.  Só roda &lt;strong&gt;depois&lt;/strong&gt; que o HTML estiver 100% carregado.&lt;/p&gt;

&lt;p&gt;Ou seja: você não precisa mais se preocupar com eventos como &lt;code&gt;DOMContentLoaded&lt;/code&gt; ou ficar jogando script pro fim do HTML só pra evitar bloqueio.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;defer&lt;/code&gt; vs &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Colocar o script no final do &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; também funciona (é o clássico, né?), mas se você tem múltiplos scripts ou quiser centralizar suas importações, o &lt;code&gt;defer&lt;/code&gt; é uma ótima solução.&lt;/p&gt;

&lt;p&gt;Com ele, você mantém os scripts no &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, deixando o HTML mais limpo e o carregamento mais previsível.&lt;/p&gt;




&lt;h3&gt;
  
  
  Resumo
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Onde e como?&lt;/th&gt;
&lt;th&gt;Comportamento&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; no &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;Bloqueia&lt;/strong&gt; o carregamento da página&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;script defer&amp;gt;&lt;/code&gt; no &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ Baixa &lt;strong&gt;em paralelo&lt;/strong&gt; e executa &lt;strong&gt;depois&lt;/strong&gt; do HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; no final do &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ Executa &lt;strong&gt;após&lt;/strong&gt; o HTML, mas exige mais atenção com organização&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Conclusão
&lt;/h3&gt;

&lt;p&gt;Se você quer um site &lt;strong&gt;mais rápido&lt;/strong&gt; e &lt;strong&gt;organizado&lt;/strong&gt;, o &lt;code&gt;defer&lt;/code&gt; é uma escolha moderna e eficiente para manipular o DOM com segurança.&lt;/p&gt;

&lt;p&gt;Às vezes, a diferença entre um site lento e uma experiência fluida está na palavrinha de cinco letras.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
