Masked phone input
Two values, one field: the person sees +1 (234) 567-8900 while a hidden input carries the raw digits, so what lands in your inbox is something you can dial or store. Formatting on input without stripping the value is the part people usually get wrong.
HTML · CSS · JavaScript
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Request a call</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
padding: 32px 16px;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
background: #f4f4f5;
color: #18181b;
}
.card {
max-width: 460px;
margin: 0 auto;
padding: 28px;
background: #fff;
border: 1px solid #e4e4e7;
border-radius: 12px;
}
h1 { margin: 0 0 6px; font-size: 20px; }
.sub { margin: 0 0 22px; font-size: 14px; color: #71717a; }
.field { margin-bottom: 16px; }
label { display: block; margin-bottom: 6px; font-size: 13px; font-weight: 500; }
input, textarea, select {
width: 100%;
padding: 10px 12px;
font: inherit;
font-size: 14px;
color: #18181b;
background: #fff;
border: 1px solid #d4d4d8;
border-radius: 8px;
}
input:focus, textarea:focus, select:focus {
outline: 2px solid #18181b;
outline-offset: 1px;
border-color: transparent;
}
textarea { min-height: 110px; resize: vertical; }
button {
width: 100%;
padding: 11px 16px;
font: inherit;
font-size: 14px;
font-weight: 500;
color: #fff;
background: #18181b;
border: 0;
border-radius: 8px;
cursor: pointer;
}
button:hover { background: #3f3f46; }
button:active { transform: scale(0.99); }
button[disabled] { opacity: 0.6; cursor: default; }
/* Hidden from people, filled in by bots. Never remove it. */
.hp { display: none; }
.hint { margin: 6px 0 0; font-size: 12px; color: #a1a1aa; }
</style>
</head>
<body>
<form class="card" action="https://app.portform.co/api/submit" method="POST">
<h1>Request a call</h1>
<p class="sub">Leave a number and we'll ring you back.</p>
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="checkbox" name="botcheck" class="hp" tabindex="-1" autocomplete="off" />
<div class="field">
<label for="name">Name</label>
<input id="name" type="text" name="name" autocomplete="name" required />
</div>
<div class="field">
<label for="phone">Phone</label>
<input id="phone" type="tel" inputmode="tel" autocomplete="tel" placeholder="+1 (234) 567-8900" required />
<!-- What actually gets sent: digits only, no formatting to undo later. -->
<input type="hidden" name="phone" id="phone_raw" />
<p class="hint">US format. We only call, we never text.</p>
</div>
<div class="field">
<label for="message">Anything we should know?</label>
<textarea id="message" name="message"></textarea>
</div>
<button type="submit">Request a call</button>
</form>
<script>
const input = document.getElementById('phone')
const raw = document.getElementById('phone_raw')
input.addEventListener('input', () => {
const digits = input.value.replace(/\D/g, '').slice(0, 11)
const national = digits.length === 11 && digits[0] === '1' ? digits.slice(1) : digits
const [, a, b, c] = national.match(/^(\d{0,3})(\d{0,3})(\d{0,4})$/) || []
input.value = [a && `(${a}`, a.length === 3 ? ') ' : '', b, c && `-${c}`]
.filter(Boolean)
.join('')
raw.value = digits ? `+1${national}` : ''
})
</script>
</body>
</html>Replace YOUR_ACCESS_KEY with the access key from your dashboard. Create one free. The endpoint and the honeypot are already in place.
Useful for
The part behind every one of these is a form backend. This template posts to a PortForm form endpoint, which stores each submission and sends it to your email; the docs cover the fields and responses.
Other templates
- Basic HTML contact formHTML · CSS
- Ajax contact formHTML · CSS · JavaScript
- JavaScript form validationHTML · CSS · JavaScript
- Multi-column contact formHTML · CSS
- Contact form with dark modeHTML · CSS
- Raw contact form (no CSS)HTML
- Multi-step NPS surveyHTML · CSS · JavaScript
- Multi-step survey formVue · HTML · CSS
- Contact form popupTailwindCSS · Alpine.js · HTML
- Popover feedback widgetTailwindCSS · Alpine.js · HTML
- File upload formHTML · CSS
- JavaScript file upload formHTML · CSS · JavaScript
- React contact formReact · HTML · CSS