Forms

NewsletterForm

An email box and a Subscribe button in one row, a plain form posting to your route, with a done state once it worked.

#forms#input#button#marketing#no-js

Demo

Preview

Stay in the loop

One email a month, no more.

Not sent yet.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/newsletter_form.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/newsletter_form.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/newsletter_form.qwe
component NewsletterFormDemoLive() {    state sent = null    <div.demo-stack>        {sent == null            ? <NewsletterForm action="#" id="newsletter-form-demo-live" title="Stay in the loop" text="One email a month, no more."                              button="Join" on_submit={(email) => { sent = email }}/>            : <NewsletterForm done={true} id="newsletter-form-demo-done" title="Stay in the loop"/>}        <p.demo-note>{sent == null ? "Not sent yet." : `on_submit got ${sent}.`}</p>    </div>}view NewsletterFormDemo() =    <div.demo-stack>        <NewsletterForm action="/subscribe" list="product" id="newsletter-form-demo-plain"/>        <NewsletterFormDemoLive/>        <NewsletterForm action="/subscribe" small={true} value="ada@example" id="newsletter-form-demo-error"                        error="That does not look like an email address."/>    </div>

Source

ui/newsletter_form.qwe
// NewsletterForm: an email box and a Subscribe button in one row, as a plain// <form method="post"> to your route, and a done state to show once it worked.////   <NewsletterForm action="/subscribe"/>//   <NewsletterForm action="/subscribe" title="Stay in the loop" text="One email a month, no more."//                   button="Join" placeholder="you@work.com"/>//   <NewsletterForm done={true}/>//   <NewsletterForm action="/subscribe" value={email} error="That does not look like an email address."/>//// action: where it posts (`name` "email"; also `list` when given, to tell lists// apart). title and text: a heading and a line above the row (none by default).// button ("Subscribe"), placeholder ("you@example.com"), label (the box's name for// screen readers, "Email address"). value: an email to fill in again. error: a// message under the row; it also marks the box aria-invalid. done: shows done_text// ("Thanks, you're subscribed. Check your inbox to confirm.") in place of the row.// small (false): the box and button in the small size. id: the box's id, unique on// the page ("newsletter-form-email"). The route reads { form: { email: str } }.// on_submit(email): inside one of your components, called with the email when the// form is sent, in place of posting it (send it yourself, with a server fn, then// show done). Without on_submit the form posts to `action`.import ui/theme.qwe use ui_clslet newsletter_form_css = css`.ui-newsletter-form { display: grid; gap: 10px; min-width: 0; font: 14px/1.45 var(--ui-font); color: var(--ui-fg); }.ui-newsletter-form-title { margin: 0; font-size: 18px; font-weight: 650; line-height: 1.25; }.ui-newsletter-form-text { margin: 0; color: var(--ui-muted); }.ui-newsletter-form-fields { display: contents; }.ui-newsletter-form-form { display: flex; gap: 8px; margin: 0; min-width: 0; }.ui-newsletter-form-input {  flex: 1; min-width: 0; height: 40px; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm);  background: var(--ui-surface); color: var(--ui-fg); font: 14px var(--ui-font); transition: border-color .15s, box-shadow .15s;}.ui-newsletter-form-input::placeholder { color: var(--ui-muted); opacity: 1; }.ui-newsletter-form-input:hover { border-color: var(--ui-strong); }.ui-newsletter-form-input:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-newsletter-form-input[aria-invalid="true"] { border-color: var(--ui-danger); }.ui-newsletter-form-button {  flex: none; display: inline-flex; align-items: center; justify-content: center; height: 40px; padding: 0 18px; border: 0;  border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font: 500 14px var(--ui-font);  white-space: nowrap; cursor: pointer; transition: background .15s, box-shadow .15s;}.ui-newsletter-form-button:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-newsletter-form-button:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-newsletter-form-sm .ui-newsletter-form-input, .ui-newsletter-form-sm .ui-newsletter-form-button { height: 32px; font-size: 13px; }.ui-newsletter-form-sm .ui-newsletter-form-button { padding: 0 12px; }.ui-newsletter-form-error { margin: 0; font-size: 13px; color: var(--ui-danger); }.ui-newsletter-form-done {  display: flex; align-items: center; gap: 10px; margin: 0; padding: 10px 14px; border-radius: var(--ui-radius-sm);  background: var(--ui-success-soft); color: var(--ui-success); font-weight: 500;}.ui-newsletter-form-done svg { flex: none; width: 18px; height: 18px; }@media (max-width: 420px) { .ui-newsletter-form-form { flex-direction: column; } .ui-newsletter-form-button { width: 100%; } }@media (prefers-reduced-motion: reduce) { .ui-newsletter-form-input, .ui-newsletter-form-button { transition: none; } }`view NewsletterForm(action: str = "/subscribe", title: str? = null, text: str? = null, button: str = "Subscribe",                    placeholder: str = "you@example.com", label: str = "Email address", value: str? = null,                    error: str? = null, done: bool = false,                    done_text: str = "Thanks, you're subscribed. Check your inbox to confirm.", list: str? = null,                    small: bool = false, id: str = "newsletter-form-email", on_submit = null, class: str? = null) {    style global newsletter_form_css    let fields = <div.ui-newsletter-form-fields>        {list != null && <input type="hidden" name="list" value={list}/>}        <input.ui-newsletter-form-input type="email" id={id} name="email" value={value} placeholder={placeholder}               required={true} autocomplete="email" autocapitalize="off" spellcheck="false" aria-label={label}               aria-invalid={error != null ? "true" : null} aria-describedby={error != null ? `${id}-error` : null}/>        <button.ui-newsletter-form-button type="submit">{button}</button>    </div>    <div class={ui_cls(["ui-newsletter-form", small && "ui-newsletter-form-sm", class])}>        {title != null && <h3.ui-newsletter-form-title>{title}</h3>}        {text != null && <p.ui-newsletter-form-text>{text}</p>}        {done            ? <p.ui-newsletter-form-done role="status">                <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"                     stroke-linejoin="round" aria-hidden="true"><circle cx="10" cy="10" r="8"/><path d="M6.5 10.5l2.5 2.5 4.5-5"/></svg>                {done_text}            </p>            : (on_submit != null                ? <form.ui-newsletter-form-form method="post" action={action} @submit={(e) => { on_submit && on_submit(e.form.email ?? "") }}>{fields}</form>                : <form.ui-newsletter-form-form method="post" action={action}>{fields}</form>)}        {!done && error != null && <p.ui-newsletter-form-error id={`${id}-error`} role="alert">{error}</p>}    </div>}
Thread
56Useful? Like it, or add to its thread below.

Thread 0 posts

On NewsletterForm: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.