A newsletter signup card built with Tailwind CSS and a few lines of vanilla JavaScript. It uses a bold neo-brutalist look (thick dark border, hard offset shadow, orange accent) and includes inline email validation, a loading state, a privacy note and a friendly success message, so it is ready for a blog sidebar, a landing page or the end of an article.
How it works
The outer .tnl-card-wrap only provides the cream page background. The card itself is the section.tnl-card, which holds two panels: the form panel marked data-tnl-form-state and the success panel marked data-tnl-success, which starts with the hidden attribute. A rotated orange square and an inline SVG envelope sit in the top-right corner as decoration.
The form uses novalidate, so the script handles validation itself. On submit it trims the value of #tnl-email and checks it against EMAIL_PATTERN. Empty or malformed input writes a message into the data-tnl-error paragraph, sets aria-invalid="true" (which turns the border red through the aria-[invalid=true]: variant) and returns focus to the field. While the user types, the error clears as soon as the address becomes valid.
A valid address adds tnl-is-loading to the card, disables the button, shows the .tnl-spinner and changes the button text to "Subscribing…". After FAKE_DELAY (1000 ms) the form panel is hidden, the address is written into data-tnl-address and the success panel is shown and focused. The data-tnl-reset button brings the empty form back. style.css adds the spinner rotation and the tnl-pop entrance of the success panel.
Customization
FAKE_DELAYinscript.js: replace thewindow.setTimeoutblock with afetch()call to your newsletter provider and show the success panel when the request resolves.bg-orange-500,bg-orange-400andfocus:ring-orange-300: swaporangeforskyorvioletto restyle the button and focus rings.shadow-[8px_8px_0_0_#1c1917]on.tnl-card: lower the offset or useshadow-xlfor a softer, classic card.border-2 border-stone-900: change toborder border-stone-200for a lighter outline on the card, input and button.max-w-lgon.tnl-card: usemax-w-mdfor a narrow sidebar ormax-w-2xlfor a wide footer band.EMAIL_PATTERNinscript.js: tighten or relax the client-side email check.- Edit the
Every Thursdaybadge and the#tnl-privacytext to match your sending schedule and policy.
Accessibility
The input has a visible label, autocomplete="email" and aria-describedby pointing to both the privacy note and the error message, so screen readers announce them together. The error paragraph is an aria-live="polite" region, and a visually hidden role="status" element reports the loading and success states. Focus moves to the success panel (tabindex="-1") after subscribing and back to the input on reset. All controls have a thick visible focus ring, decorative SVGs use aria-hidden="true", and animations and hover movement are disabled under prefers-reduced-motion: reduce.
Usage notes
The script initializes every .tnl-card on the page. For several cards, give each input and its related paragraphs unique id values.