https://github.com/smastrom/react-email-autocomplete
💌 Headless email input field with custom suggestions. Inspired by european flight booking websites.
https://github.com/smastrom/react-email-autocomplete
react react-autocomplete react-autosuggest react-email react-email-component
Last synced: about 1 year ago
JSON representation
💌 Headless email input field with custom suggestions. Inspired by european flight booking websites.
- Host: GitHub
- URL: https://github.com/smastrom/react-email-autocomplete
- Owner: smastrom
- License: mit
- Created: 2022-09-17T09:38:41.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2023-12-10T21:39:53.000Z (over 2 years ago)
- Last Synced: 2025-04-10T08:15:02.723Z (about 1 year ago)
- Topics: react, react-autocomplete, react-autosuggest, react-email, react-email-component
- Language: TypeScript
- Homepage: https://react-email-autocomplete.netlify.app
- Size: 412 KB
- Stars: 15
- Watchers: 2
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# React Email Autocomplete
 

| 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.
- Completely unstyled and white labeled (ships with zero CSS)
- Fully accessible with superlative keyboard controls
- Forward any event and attribute to the `` element or control it with React Hook Form
[Demo and examples](https://@smastrom/react-email-autocomplete.netlify.app) — [Stackblitz](https://stackblitz.com/edit/react-4kufqv?file=src/App.js) — [NextJS](https://stackblitz.com/edit/stackblitz-starters-f36nmm?file=app%2Fpage.tsx)
## :floppy_disk: Installation
```bash
pnpm add @smastrom/react-email-autocomplete
# npm i @smastrom/react-email-autocomplete
# yarn add @smastrom/react-email-autocomplete
```
## :art: Usage / Styling
The component renders a single `div` with a very simple structure:
```js
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:
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```
NextJS App Router
`components/Email.tsx`
```tsx
'use client'
import { useState } from 'react'
import { Email as EmailAutocomplete } 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']
export function Email() {
const [email, setEmail] = useState('')
return (
customSetter(newValue)
value={email}
/>
)
}
```
`app/page.tsx`
```tsx
import { Email } from '@/components/Email'
export default function Home() {
return (
{/* ... */}
{/* ... */}
)
}
```
TypeScript
```ts
import type { ClassNames } from '@smastrom/react-email-autocomplete'
const myClassNames: ClassNames = {
wrapper: 'my-wrapper',
input: 'my-input',
}
```
Tailwind Intellisense
You can add a this property in VSCode's `settings.json` in order to enable autcomplete for any object property or variable ending with `ClassNames`.
```json
"tailwindCSS.experimental.classRegex": [
["ClassNames \\=([^;]*);", "'([^']*)'"],
["ClassNames \\=([^;]*);", "\"([^\"]*)\""],
["ClassNames \\=([^;]*);", "\\`([^\\`]*)\\`"]
],
```
Basic styles
This package ships with **zero css**. Initial styles enough to see the component in action may match the following properties:
```css
.my-wrapper,
.my-input {
position: relative;
}
.my-input,
.my-dropdown,
.my-suggestion {
font-size: inherit;
box-sizing: border-box;
width: 100%;
}
.my-dropdown {
position: absolute;
margin: 0.45rem 0 0 0;
padding: 0;
list-style-type: none;
z-index: 999;
}
.my-suggestion {
cursor: pointer;
user-select: none;
overflow: hidden;
}
```
### Focus/Hover styles
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).
```css
.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:
```jsx
```
```css
.my-suggestion[data-custom-attr='true'] {
background-color: aliceblue;
}
```
## :dna: Modes
### 1. Basic Mode
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 `@` |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|  |  |
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```
### 2. Refine Mode (optional)
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](https://github.com/smastrom/@smastrom/react-email-autocomplete/blob/main/src/domains.json) of the ~160 most popular world domains that you can directly import and use (thanks to **@mailcheck**):
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```
Alternatively, you can use your own array of domains or [search]() for the one that best suits your audience.
## :globe_with_meridians: Localization
This package ships with an optional hook that simplifies managing different lists of domains according to the [browser's locale](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language).
**1 - Create an object and define lists for each browser locale:**
```js
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',
// ...
],
}
```
TypeScript
```ts
import type { LocalizedList } from '@smastrom/react-email-autocomplete'
export const emailProviders: LocalizedList = {
default: [
'gmail.com',
'yahoo.com',
'hotmail.com',
'aol.com',
// ...
],
// ...
}
```
You may define [lang codes](https://www.localeplanet.com/icu/iso639.html) 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:**
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```
### Usage with internationalization frameworks or SSR
To manually set the locale, pass its code as second argument:
```jsx
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 (
customSetter(newValue)
value={email}
/>
)
}
```
Or with NextJS App router:
`components/Email.tsx`
```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 (
)
}
```
`app/page.tsx`
```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 (
)
}
```
## :8ball: onSelect callback
To invoke a callback everytime a suggestion is selected (either with mouse or keyboard), pass a callback to `onSelect` prop:
```jsx
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 (
customSetter(newValue)
onSelect={handleSelect}
value={email}
/>
)
}
```
Type Definition
```ts
type OnSelectData = {
value: string
keyboard: boolean
position: number
}
type OnSelect = (object: OnSelectData) => void | Promise
```
## :cyclone: Props
| 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.
## :keyboard: Keyboard controls
- **↑ ↓** - Navigate through suggestions / input
- **← →** - Move cursor and focus the input field while keeping list open
- **Backspace / Alphanumeric keys** - Edit the input value and keep refining suggestions
- **Enter / Space** - Confirm the suggestion
- **Escape** - Close the list and focus the input field
- **Tab / Shift + Tab** - Close the list and go to next/prev focusable input
## React Hook Form
No special configuration needed, it just works. Just follow the official React Hook Form's [Controller documentation](https://react-hook-form.com/api/usecontroller/controller).
## :dvd: License
MIT