Before typing @ |
After typing @ (optional) |
---|---|
React Email Autocomplete is an unstyled, zero-dependency component inspired by some european flight booking websites. As soon as users start typing their email address, it will suggest the most common email providers.
<input />
element or control it with React Hook FormDemo and examples — Stackblitz — NextJS
pnpm add @smastrom/react-email-autocomplete
# npm i @smastrom/react-email-autocomplete
# yarn add @smastrom/react-email-autocomplete
The component renders a single div
with a very simple structure:
Wrapper — div
├── Email Input Field — input
└── Dropdown — ul
└── Suggestions - li[]
└──[username - span:first-of-type] [@domain.com - span:last-of-type]
Specify classNames
for each element you'd like to style:
import { Email } from '@smastrom/react-email-autocomplete'
const classNames = {
wrapper: 'my-wrapper',
input: 'my-input',
dropdown: 'my-dropdown',
suggestion: 'my-suggestion',
username: 'my-username',
domain: 'my-domain',
}
const baseList = ['gmail.com', 'yahoo.com', 'hotmail.com', 'aol.com', 'msn.com']
function App() {
const [email, setEmail] = useState('')
return (
<Email
classNames={classNames}
baseList={baseList}
onChange={setEmail} // or (newValue) => customSetter(newValue)
value={email}
/>
)
}
Although you can target the pseudo classes :hover
and :focus
, it is recommended instead to target the attribute data-active-email
in order to avoid :hover
styles to be applied to a suggestion as soon as the dropdown is opened (in case the cursor is hovering it).
.my-suggestion[data-active-email='true'] {
background-color: aliceblue;
}
.my-suggestion:hover,
.my-suggestion:focus,
.my-suggestion:focus-visible {
outline: none;
}
The attribute name can also be customized via activeDataAttr
prop:
<Email
activeDataAttr="data-custom-attr"
classNames={{
...classNames,
suggestion: 'my-suggestion',
}}
baseList={baseList}
value={email}
/>
.my-suggestion[data-custom-attr='true'] {
background-color: aliceblue;
}
Once users start typing, it displays a list of base suggestions and hides it once they type @
. It already gives a nice UX and should be enough for the vast majority of websites:
Before typing @ |
After typing @ |
---|---|
import { Email } from '@smastrom/react-email-autocomplete'
const baseList = [
'gmail.com',
'yahoo.com',
'hotmail.com',
'aol.com',
'msn.com',
'proton.me',
]
function App() {
const [email, setEmail] = useState('')
return (
<Email
baseList={baseList}
onChange={setEmail} // or (newValue) => customSetter(newValue)
value={email}
/>
)
}
Acts like Basic Mode until users type @
. Then as they start typing the domain, it starts refining suggestions according to an extended list of domains.
Before typing @ |
After typing @ |
---|---|
All you have to do is to provide a second array of domains to refineList
prop. This package ships with a curated list of the ~160 most popular world domains that you can directly import and use (thanks to @mailcheck):
import { Email, domains } from '@smastrom/react-email-autocomplete'
const baseList = [
'gmail.com',
'yahoo.com',
'hotmail.com',
'aol.com',
'msn.com',
'proton.me',
]
function App() {
const [email, setEmail] = useState('')
return (
<Email
baseList={baseList}
refineList={domains}
onChange={setEmail} // or (newValue) => customSetter(newValue)
value={email}
/>
)
}
Alternatively, you can use your own array of domains or [search]() for the one that best suits your audience.
This package ships with an optional hook that simplifies managing different lists of domains according to the browser's locale.
1 - Create an object and define lists for each browser locale:
export const emailProviders = {
default: [
'gmail.com',
'yahoo.com',
'hotmail.com',
'aol.com',
// ...
],
it: [
'gmail.com',
'yahoo.com',
'yahoo.it',
'tiscali.it',
// ...
],
'it-CH': [
'gmail.com',
'outlook.com',
'bluewin.ch',
'gmx.de',
// ...
],
}
You may define lang codes with or without country codes.
For languages without country code (such as it
), by default it will match all browser locales beginning with it such as it
, it-CH
, it-IT
and so on.
For languages with country code (it-CH
) it will match it-CH
but not it
or it-IT
.
If you define both it-CH
and it
, it-CH
will match only it-CH
and it
will match it
, it-IT
and so on.
2 - Use the hook:
import { Email, useLocalizedList } from '@smastrom/react-email-autocomplete'
import { emailProviders } from '@/src/static/locales'
function App() {
const baseList = useLocalizedList(emailProviders)
const [email, setEmail] = useState('')
return (
<Email
baseList={baseList}
onChange={setEmail} // or (newValue) => customSetter(newValue)
value={email}
/>
)
}
To manually set the locale, pass its code as second argument:
import { useRouter } from 'next/router'
import { emailProviders } from '@/src/static/locales'
import { Email, useLocalizedList } from '@smastrom/react-email-autocomplete'
function App() {
const { locale } = useRouter()
const baseList = useLocalizedList(emailProviders, locale)
const [email, setEmail] = useState('')
return (
<Email
baseList={baseList}
onChange={setEmail} // or (newValue) => customSetter(newValue)
value={email}
/>
)
}
Or with NextJS App router:
components/Email.tsx
'use client'
import {
Email as EmailAutocomplete,
useLocalizedList,
} from '@smastrom/react-email-autocomplete'
import { emailProviders } from '@/static/locales'
export function Email({ lang }: { lang: string }) {
const baseList = useLocalizedList(emailProviders, lang)
const [email, setEmail] = useState('')
return (
<EmailAutocomplete
classNames={classNames}
baseList={baseList}
onChange={setEmail}
value={email}
/>
)
}
app/page.tsx
import { Email } from '@/components/Email'
import { headers } from 'next/headers'
export default function Home() {
const headersList = headers()
const lang = headersList.get('accept-language')?.split(',')[0]
return (
<main>
<Email lang={lang} />
</main>
)
}
To invoke a callback everytime a suggestion is selected (either with mouse or keyboard), pass a callback to onSelect
prop:
import { Email } from '@smastrom/react-email-autocomplete'
function handleSelect(data) {
console.log(data) // { value: 'johndoe@gmail.com', keyboard: true, position: 0 }
}
function App() {
const [email, setEmail] = useState('')
return (
<Email
baseList={baseList}
onChange={setEmail} // or (newValue) => customSetter(newValue)
onSelect={handleSelect}
value={email}
/>
)
}
Prop | Description | Type | Default | Required |
---|---|---|---|---|
value |
State or portion of state that holds the email value | string | undefined | :white_check_mark: |
onChange |
State setter or custom dispatcher to update the email | OnChange | undefined | :white_check_mark: |
baseList |
Domains to suggest while typing the username | string[] | undefined | :white_check_mark: |
refineList |
Domains to refine suggestions after typing @ |
string[] | [] | :x: |
onSelect |
Custom callback on suggestion select | OnSelect | () => {} | :x: |
minChars |
Minimum chars required to display suggestions | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 2 | :x: |
maxResults |
Maximum number of suggestions to display | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 6 | :x: |
classNames |
Class names for each element | ClassNames | undefined | :x: |
className |
Class name of the root element | string | undefined | :x: |
activeDataAttr |
Attribute name to set on focused/hovered suggestion | string | data-active-email |
:x: |
dropdownAriaLabel |
Aria label for the dropdown list | string | Suggestions |
:x: |
:bulb: React's ref
and any other HTMLInputElement
attribute can be passed as prop to the component and it will be forwarded to the input element.
No special configuration needed, it just works. Just follow the official React Hook Form's Controller documentation.
MIT