← Biblioteca componentes/conversao/faixa-newsletter/ficha.md CódigoTela cheia
conversao/faixa-newsletter

Faixa de newsletter com consentimento

🟢 estavel complexidade baixa prefixo nws- newsletter email formulario lgpd consentimento carta captura

Incluir · as linhas que se colam num site novo, depois do core (CLAUDE.md §10)

<link rel="stylesheet" href="componentes/conversao/faixa-newsletter/estilo.css">
<script src="componentes/conversao/faixa-newsletter/script.js"></script>

markup.html · 51 linhas · 2,5 KB

<!-- ==========================================================================
     FAIXA DE NEWSLETTER — assinar a carta, com o consentimento à vista
     Prefixo: nws-
     Requer: core (tokens, base, motion.css, motion.js) + este estilo.css e
             este script.js

     A promessa de um lado, o campo de e-mail e o botão do outro, e embaixo
     a caixa de consentimento — desmarcada, porque consentimento marcado de
     fábrica não é consentimento (LGPD, art. 8º).

     O SUCESSO SÓ APARECE SE O ENVIO ACONTECEU. O formulário manda para o
     endereço do atributo `action` e só diz "Pronto" quando a resposta volta
     com status de sucesso. SEM `action` — como aqui, no exemplo — ele não
     finge: avisa que é demonstração e que nada foi enviado. Um formulário
     que diz "inscrição confirmada" sem ter mandado nada perde todos os
     leitores em silêncio, e ninguém descobre até perguntar por que a lista
     não cresce.

     ANTES DE PUBLICAR: ponha action="https://…" (e method="post") com o
     endereço do provedor de e-mail. Troque o id="nws-email" se o
     componente aparecer duas vezes na mesma página.
     ========================================================================== -->

<section class="sec-paper sec-pad nws" data-nws>
  <div class="wrap nws-grade">

    <div class="nws-texto">
      <span class="eyebrow" data-rv>Carta mensal</span>
      <h2 class="sec-title ttl">Uma carta por mês, <em>sem oferta</em></h2>
      <p class="sec-lead" data-rv data-d="1">Mercado, patrimônio e sucessão em dez minutos de leitura. Sai na primeira segunda-feira de cada mês.</p>
    </div>

    <form class="nws-form" data-nws-form data-rv data-d="1" novalidate>
      <div class="nws-linha">
        <div class="field nws-campo">
          <label for="nws-email">Seu e-mail</label>
          <input type="email" id="nws-email" name="email" placeholder="voce@email.com" required autocomplete="email">
        </div>
        <button class="btn btn-primary nws-enviar" type="submit">Assinar</button>
      </div>

      <label class="nws-consentimento">
        <input class="nws-caixa" type="checkbox" name="consentimento" value="sim" required>
        <span class="nws-consentimento-t">Aceito receber a carta por e-mail e li a <a href="#privacidade">política de privacidade</a>. Dá para cancelar com um clique em qualquer carta.</span>
      </label>

      <p class="nws-retorno" data-nws-retorno role="status" aria-live="polite"></p>
    </form>

  </div>
</section>

estilo.css · 65 linhas · 2,7 KB

/* ==========================================================================
   FAIXA DE NEWSLETTER — nws-
   Promessa de um lado; campo, botão e consentimento do outro.
   Tokens usados: --fg --fg-2 --fg-muted --rule-2 --accent --accent-ink --ease
   Usa a primitiva .field do core para o campo (rótulo + régua que acende).
   ========================================================================== */

.nws-grade{
  display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr);
  gap:clamp(32px,6vw,96px);align-items:end;
}

.nws-form{display:grid;gap:18px}

/* O campo cresce, o botão fica do tamanho do texto. Alinhados pela base,
   para a régua do .field e a pílula do botão pousarem na mesma linha. */
.nws-linha{display:flex;align-items:flex-end;gap:16px}
.nws-campo{flex:1 1 auto;min-width:0}
.nws-enviar{flex:0 0 auto}
.nws-enviar[aria-busy="true"]{opacity:.6;cursor:progress}
.nws-enviar[aria-disabled="true"]{pointer-events:none}

/* ---------------------------------------------------------------- consentimento
   A caixa NATIVA, com a cor de marca por accent-color: vem de graça com
   foco, teclado, leitor de tela e o estado marcado anunciado. Um quadrado
   desenhado à mão teria de reimplementar os quatro. */
.nws-consentimento{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:start;
  font-size:13px;line-height:1.55;color:var(--fg-2);cursor:pointer;
}
.nws-caixa{
  inline-size:17px;block-size:17px;margin:1px 0 0;
  accent-color:var(--accent);cursor:pointer;
}
.nws-consentimento-t a{color:var(--fg);text-decoration:underline;text-underline-offset:3px}
.nws-consentimento-t a:hover{color:var(--accent-ink,var(--accent))}
.nws-consentimento.is-faltando .nws-consentimento-t{color:var(--fg)}

/* ---------------------------------------------------------------- retorno
   Uma linha que o leitor de tela anuncia (role=status). Vazia, não ocupa
   espaço. O estado vai por EXTENSO no texto, e o sinal antes dele repete —
   a cor nunca carrega sozinha a diferença entre erro e sucesso. */
.nws-retorno:empty{display:none}
.nws-retorno{
  display:flex;gap:10px;align-items:baseline;
  font-size:13.5px;line-height:1.55;color:var(--fg-2);
}
.nws-retorno::before{
  flex:0 0 auto;
  font-family:var(--display);font-weight:700;font-size:12px;
  color:var(--fg-muted);
}
.nws-retorno.is-ok{color:var(--fg)}
.nws-retorno.is-ok::before{content:"✓";color:var(--accent-ink,var(--accent))}
.nws-retorno.is-erro{color:var(--fg)}
.nws-retorno.is-erro::before{content:"!"}
.nws-retorno.is-aviso::before{content:"i"}

@media(max-width:900px){
  .nws-grade{grid-template-columns:1fr;align-items:start}
}
@media(max-width:480px){
  .nws-linha{flex-direction:column;align-items:stretch}
  .nws-enviar{width:100%}
}

script.js · 98 linhas · 4,0 KB

/* ==========================================================================
   FAIXA DE NEWSLETTER — nws-
   Valida, envia de verdade, e só então diz "Pronto".

     sem `action` no <form>   não envia e AVISA que é demonstração
     com `action`             envia por fetch (FormData, no method do form)
                              e diz "Pronto" só com resposta de sucesso;
                              qualquer outra coisa vira mensagem de erro

   Sem JavaScript o formulário continua um formulário: com `action`, o
   navegador envia sozinho e o `required` nativo valida. O script só troca a
   página de resposta do provedor por uma linha na própria dobra.

   Nada é guardado no navegador: nem o e-mail, nem o "já assinei".
   Não anima nada.
   ========================================================================== */
(function () {
  var forms = document.querySelectorAll('[data-nws] [data-nws-form]');
  if (!forms.length) return;                   // guard: sem o componente, saída silenciosa

  [].forEach.call(forms, function (form) {
    var email = form.querySelector('input[type="email"]');
    var caixa = form.querySelector('.nws-caixa');
    var botao = form.querySelector('.nws-enviar');
    var retorno = form.querySelector('[data-nws-retorno]');
    var rotulo = botao ? botao.textContent : '';
    if (!email || !retorno) return;

    function dizer(tipo, texto) {
      retorno.className = 'nws-retorno' + (tipo ? ' is-' + tipo : '');
      retorno.textContent = texto;
    }

    /* aria-disabled e não `disabled`: desabilitar o botão que tem o foco joga
       o foco para o <body>, e quem enviou pelo teclado se perde no meio da
       página justo quando a mensagem de erro aparece. Medido: depois de um
       envio que falhou, document.activeElement era o <body>. O clique
       duplicado é barrado pela flag. */
    var enviando = false;
    function ocupado(sim) {
      enviando = sim;
      if (!botao) return;
      botao.setAttribute('aria-disabled', sim ? 'true' : 'false');
      botao.setAttribute('aria-busy', sim ? 'true' : 'false');
      botao.textContent = sim ? 'Enviando…' : rotulo;
    }

    /* Limpa o aviso de erro assim que a pessoa corrige, em vez de deixar a
       mensagem velha contradizendo o campo já certo. */
    email.addEventListener('input', function () {
      email.removeAttribute('aria-invalid');
      if (retorno.className.indexOf('is-erro') >= 0) dizer('', '');
    });
    if (caixa) caixa.addEventListener('change', function () {
      caixa.parentNode.classList.remove('is-faltando');
    });

    form.addEventListener('submit', function (e) {
      e.preventDefault();
      if (enviando) return;

      if (!email.value.trim() || !email.checkValidity()) {
        email.setAttribute('aria-invalid', 'true');
        dizer('erro', 'Confira o e-mail: ele precisa ter o formato nome@dominio.com.');
        email.focus();
        return;
      }
      if (caixa && !caixa.checked) {
        caixa.parentNode.classList.add('is-faltando');
        dizer('erro', 'Para assinar, marque que aceita receber a carta.');
        caixa.focus();
        return;
      }

      var destino = form.getAttribute('action');
      if (!destino) {
        /* DEMONSTRAÇÃO. Não finge sucesso: diz o que aconteceu. */
        dizer('aviso', 'Demonstração: este formulário ainda não tem endereço de envio, e nada foi enviado.');
        return;
      }

      ocupado(true);
      fetch(destino, {
        method: (form.getAttribute('method') || 'post').toUpperCase(),
        body: new FormData(form),
        headers: { Accept: 'application/json' }
      }).then(function (r) {
        if (!r.ok) throw new Error(r.status);
        ocupado(false);
        dizer('ok', 'Pronto. A próxima carta chega em ' + email.value.trim() + '.');
        [].forEach.call(form.querySelectorAll('input, button'), function (el) { el.disabled = true; });
      }).catch(function () {
        ocupado(false);
        dizer('erro', 'Não foi possível assinar agora. Tente de novo em alguns minutos.');
      });
    });
  });
})();

A promessa da carta de um lado; do outro, o campo de e-mail, o botão e, embaixo, a caixa de consentimento — desmarcada. Ao enviar, uma linha na própria dobra diz o que aconteceu: "Pronto. A próxima carta chega em voce@email.com." — mas só se o envio de fato aconteceu.

O problema que ela resolve é duplo. O editorial: newsletter de assessoria é a forma mais barata de manter relacionamento com quem ainda não é cliente, e a faixa típica pede e-mail sem dizer o que vem. O técnico: o formulário comum de biblioteca confirma sem enviar — mostra "inscrição confirmada" antes de alguém plugar o provedor, e o site vai ao ar assim. Este não finge.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Olho 1 a 3 palavras, o nome do envio. Opcional
Título 3 a 8 palavras: o conteúdo e a frequência, nunca "Assine nossa newsletter"
Apoio 10 a 28 palavras: o que vem, quanto tempo de leitura, quando chega
Botão 1 a 2 palavras: Assinar, Receber a carta
Consentimento 12 a 36 palavras: o que se aceita, a política de privacidade e como cancelar

O que mantém a peça: a frequência escrita. "Uma carta por mês" responde a pergunta que trava a assinatura — vão encher minha caixa? — antes de ela ser feita.

Especificação dos assets

Nenhum.


Como usar

<link rel="stylesheet" href="componentes/conversao/faixa-newsletter/estilo.css">
<script src="componentes/conversao/faixa-newsletter/script.js"></script>

Antes de publicar, ponha no <form> o endereço do provedor de e-mail:

<form class="nws-form" data-nws-form action="https://provedor.exemplo/assinar" method="post" novalidate>

Ganchos

Gancho Papel
[data-nws] a raiz
[data-nws-form] o formulário. O action dele é o destino; o method, o verbo
[data-nws-retorno] a linha de retorno, role="status", preenchida pelo script
.nws-retorno.is-ok / .is-erro / .is-aviso sucesso, erro de validação ou de envio, demonstração

Variações


Compliance


Gotchas


Movimento reduzido / toque

Nada anda. No celular, abaixo de 480px, o botão desce para baixo do campo e ocupa a largura toda.


Histórico

Origem Quando O quê
autoral 2026-09-29 Construído do zero. Padrão de domínio comum, sem referência única

Origem. A faixa de newsletter das bibliotecas de blocos e dos sites de research — um padrão tão difundido que não tem dono. Nada foi copiado. O registro consolidado vai em projetos/autorais.md.

O que ela devolve à casa. O envio honesto — sucesso só com resposta, demonstração dita como demonstração — é o mesmo método das cotações e da agenda aplicado a formulário. O conversao/cta-formulario-lead recebeu a mesma correção no mesmo dia, e com ela saiu de revisar: era a última pendência aberta da biblioteca.