In an HTML app, use the right input types (email, tel, number, date) and inputmode so Android shows the correct keyboard, add autocomplete attributes for autofill, validate with built-in constraints plus friendly messages, and submit with fetch() to an https API or form service that allows cross-origin requests from the app.
Key takeaways
type,inputmodeandenterkeyhintcontrol Android's keyboard.- There is no PHP inside an APK — forms must post to a server on the internet.
- Use
fetch()and show the result in-page rather than a full form POST. - Save drafts to localStorage so a rotation or interruption loses nothing.
Get the right keyboard
| You need | Markup | Android shows |
|---|---|---|
type="email" autocomplete="email" | Keyboard with @ and .com | |
| Phone | type="tel" autocomplete="tel" | Phone dial pad |
| Whole number (PIN, quantity) | inputmode="numeric" pattern="[0-9]*" | Number pad |
| Price, weight | inputmode="decimal" | Numbers with decimal point |
| Search | type="search" enterkeyhint="search" | Search key instead of Enter |
| Date | type="date" | Native date picker |
| One-time code | autocomplete="one-time-code" inputmode="numeric" | Number pad |
Avoid type="number" for things that are not quantities (card numbers, postcodes): it drops leading zeros and adds spinner behaviour.
Keep fields visible above the keyboard
When the keyboard opens, the WebView shrinks. Content in a position: fixed footer gets pushed up or hidden. Put forms in normal flow, and scroll the focused field into view if your layout needs it:
document.addEventListener('focusin', e => {
if (e.target.matches('input, textarea, select'))
setTimeout(() => e.target.scrollIntoView({ block: 'center', behavior: 'smooth' }), 300);
});Validation people understand
<form id="f" novalidate>
<label>Email <input name="email" type="email" required autocomplete="email"></label>
<p class="err" id="email-err" hidden>Enter an email like name@example.com</p>
<button>Send</button>
</form>
<script>
f.addEventListener('submit', e => {
e.preventDefault();
const ok = f.email.checkValidity();
document.getElementById('email-err').hidden = ok;
if (ok) send(new FormData(f));
});
</script>The built-in bubble messages work but look like a browser; in-page messages look like an app.
Submitting: there is no server in the APK
Your files are served from inside the app, so action="contact.php" goes nowhere. Post to something on the internet:
async function send(data) {
btn.disabled = true;
try {
const r = await fetch('https://formspree.io/f/yourFormId', {
method: 'POST', body: data, headers: { Accept: 'application/json' } });
show(r.ok ? 'Thanks — we got it.' : 'Something went wrong. Try again.');
} catch {
show('You are offline. Your message is saved and will send when you reconnect.');
queue(data); // see the dashboard guide for a send queue
} finally { btn.disabled = false; }
}For your own API, allow cross-origin requests from https://appassets.androidplatform.net. A mailto: link is a zero-backend alternative for simple contact forms — in builds that pass links to Android, it opens the user's email app.
Save drafts
Long forms should survive the app being closed. Write each field to localStorage on input and restore on load; clear it after a successful submit.
File inputs
<input type="file"> needs the app shell to open Android's picker. Test it on your build before relying on it, and offer a fallback (such as an email address) for attachments.
Frequently asked questions
Can an HTML form in an APK send email?
Not by itself. Post the form to a form service or your own API that sends the email, or use a mailto: link to open the user's email app.
Why is my form field hidden by the keyboard?
The field is in a fixed-position container or below content that does not scroll. Keep forms in normal page flow and scroll the focused input into view.