Email input
An email address field that offers the common mail domains once the @ is typed.
On this page
Code
import { EmailInput } from "@bosunkit/components/email-input";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"A crew member's email, required: after the @ the field offers the common mail domains and a sailing one the page " +
"adds, narrowing as the crew type; arrows and Enter take one, and an address without its domain is named once the " +
"crew leave the field.";
const DOMAINS: readonly string[] = [
"gmail.com",
"hotmail.com",
"yahoo.com",
"outlook.com",
"icloud.com",
"sailmail.com",
];
export default function Showcase(): ReactElement {
const [email, setEmail]: [string, Dispatch<SetStateAction<string>>] = useState<string>("");
return (
<div style={{ width: "100%", maxWidth: 400, display: "grid", gap: "var(--space-sm)" }}>
<EmailInput
label="Email"
required
domains={DOMAINS}
value={email}
onValueChange={setEmail}
placeholder="name@example.com"
hint="For the crew list and the weather briefings."
/>
</div>
);
}
An email input is a type="email" field the browser can fill (autoComplete="email"), with a phone's email
keyboard. Once the crew have typed a name and an @, it lists whole addresses for the mail domains in domains
(gmail.com, hotmail.com, yahoo.com, outlook.com and icloud.com by default), narrowed to those that start with what
follows the @. Arrow keys move through the list, Enter or Tab takes an address, Escape closes the list, and a
press on one takes it without losing the focus.
The field takes the shape bosund's forms give an email field: a name, one @, and a domain with a dot inside it.
When the crew leave the field with an address that lacks one of them, the error names the part to fix: a space to
remove, the @, a second @, the name, or the domain.
Examples
Empty
Code
import { EmailInput } from "@bosunkit/components/email-input";
import type { ReactElement } from "react";
export const description: string =
"An empty email field: the browser can fill it, and a phone shows its email keyboard.";
export default function Empty(): ReactElement {
return <EmailInput label="Email" />;
}
Domains after the @
Code
import { EmailInput } from "@bosunkit/components/email-input";
import type { ReactElement } from "react";
export const description: string =
"Typed up to the @, the field lists the five common domains; press the field and type a letter to narrow them.";
export default function Suggestions(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: 400 }}>
<EmailInput label="Email" defaultValue="mara.quist@" />
</div>
);
}
Invalid
Code
import { EmailInput } from "@bosunkit/components/email-input";
import type { ReactElement } from "react";
export const description: string =
"An address bosund's form pattern would refuse, here one without a domain: the error names the part that is " +
"missing.";
export default function Invalid(): ReactElement {
return (
<EmailInput label="Email" defaultValue="mara.quist@gmail" error="Add the domain after the @, such as gmail.com." />
);
}
Read-only
Code
import { EmailInput } from "@bosunkit/components/email-input";
import type { ReactElement } from "react";
export const description: string = "A read-only address offers no domains.";
export default function ReadOnly(): ReactElement {
return <EmailInput label="Owner's email" defaultValue="owner@example.org" readOnly />;
}
Disabled
Code
import { EmailInput } from "@bosunkit/components/email-input";
import type { ReactElement } from "react";
export const description: string = "A disabled email field.";
export default function Disabled(): ReactElement {
return <EmailInput label="Email" defaultValue="ines.varga@example.org" disabled />;
}
Inside a form field
Code
import { EmailInput } from "@bosunkit/components/email-input";
import { FormField } from "@bosunkit/components/form-field";
import type { ReactElement } from "react";
export const description: string = "Inside a form field, named by `aria-label`.";
export default function InFormField(): ReactElement {
return (
<FormField label="Marina office">
<EmailInput aria-label="Marina office email" defaultValue="office@example.org" />
</FormField>
);
}
Paste
The paste button, at the end of the field, fills it with the address in the clipboard. It reads:
- a
mailto:link, without its subject or other query; - a name with the address in angle brackets (
Mara Quist <mara.quist@example.org>); - the first address in a sentence.
The address it takes has the shape bosund's forms require. The paste button shows only when the clipboard holds something the field can take, named by what it would paste (Paste mara.quist@example.org). To know that, the field reads the clipboard when it takes focus and when the window comes back, and only where the browser lets the page read it without asking: the clipboard-read permission is granted. Where the browser would ask first, or does not know the permission (Firefox, Safari), the field reads nothing and shows no button, so no prompt appears on focus; Ctrl+V and a long press on a phone paste through the same reader. A read-only or disabled field shows no paste button.
- mailto:mara.quist@example.org?subject=Crew%20list
- Mara Quist <mara.quist@example.org>
- Write to mara.quist@example.org before Friday.
- Hello
Code
import { EmailInput } from "@bosunkit/components/email-input";
import type { ReactElement } from "react";
export const description: string =
"Copy one of these lines, then focus the field: where the browser lets the page read the clipboard, a paste button named by the address appears; Ctrl+V works everywhere. The address comes out of a mailto link, a named address or a sentence.";
const SAMPLES: readonly string[] = [
"mailto:mara.quist@example.org?subject=Crew%20list",
"Mara Quist <mara.quist@example.org>",
"Write to mara.quist@example.org before Friday.",
"Hello",
];
export default function Paste(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: 520, display: "grid", gap: "var(--space-md)" }}>
<EmailInput label="Email" />
<ul style={{ margin: 0, paddingLeft: "var(--space-lg)", fontFamily: "var(--font-mono)", fontSize: 12 }}>
{SAMPLES.map((sample: string): ReactElement => (
<li key={sample} style={{ userSelect: "all", overflowWrap: "anywhere" }}>
{sample}
</li>
))}
</ul>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | undefined | – | The field's name on the outline, translated by the caller; omit it inside a FormField. |
| value | string | undefined | – | The address as typed. |
| defaultValue | string | undefined | – | The address it starts with when the caller does not hold value. |
| onValueChange | ((value: string) => void) | undefined | – | Called with the text as the crew type it, and with the whole address when they choose a suggestion. |
| domains | readonly string[] | undefined | EMAIL_DOMAINS | The domains offered after the @, in this order; the five common mail domains by default. |
| hint | string | undefined | – | Text under the field, translated by the caller. |
| error | string | undefined | – | The caller's own error, shown in place of the field's. |
| required | boolean | undefined | – | Whether the field must hold an address. |
| disabled | boolean | undefined | – | Whether the field takes no input. |
| readOnly | boolean | undefined | – | Whether the field shows its address without letting the crew change it. |
| id | string | undefined | – | The input's id, for a Label's htmlFor. |
| name | string | undefined | – | The form field name. |
| placeholder | string | undefined | – | Text shown while the field is empty and focused, translated by the caller. |
| aria-label | string | undefined | – | Names the input when there is no label. |
| className | string | undefined | – | Classes added to the field's root. |
| testId | string | undefined | – | data-testid on the field. |
| ref | Ref<HTMLInputElement> | undefined | – | The input element. |
| motion | MotionProp | undefined | – | false opens the suggestion list at once and keeps the error line still; true also moves the error line. The
list fades in by default, as the combo box's does. |
Related
- Outlined field: the box every maritime input draws in.
- Phone input: the other contact field of a crew member.
- Autocomplete: free text with suggestions from a request.