Carl Eubanks
The date input that made me question my life choices
Now, before we get into anything, there’s some important context I need to provide.
What is a date input field? Well, if we do a quick interwebs search, you’ll probably get something like:
“A date input is an HTML form element that allows users to enter a date value.”
This feels like a lot of fluff, so let me save you the trip: it’s a text box that’s supposed to make entering dates easier, but somehow makes everyone’s life worse.
The cursor position principle
Every time I fill out an online form (and I’ve filled out far too many) that requires me to prove I’m old enough to exist, I get a gentle slap. Date inputs are where good UX goes to lie down and take a very long nap.
Why do you care so much?
I don’t.
But the forms have forced me to invent a principle with a generic name that states the obvious:
Given user input, maintain the user’s mental model of where they are in the input stream—even when you’re reformatting behind the scenes.
It’s not about whether the formatting is “correct.”
It’s about consistency, predictability, and not making users feel like they’re fighting for their lives just to enter a date.
The problem
(Or: Why I spent three hours writing ~80 lines of code.)

The requirement seemed simple:
“Auto-format the date to MM/DD/YYYY so we always have the right output.”
If I left it up to Preact, I probably could have fulfilled the requirement with a sprinkle of zhuzh. But the problem is: I love the build. So… why not do it myself?
The solution
Read: the work-from-home dad who realized he only had a few quiet hours before the kids got home, and that work is not a build-anything playground. But like all who walk into Home Depot for “just one thing,” I found the magic that forces you to keep building because you’ve GOT to do it.
Here’s the key insight I took away:
The cursor doesn’t care about your formatting. It cares about its position. In an unformatted string, the cursor is sitting at the nth numeric character. In a formatted string, that character might be shifted by two slashes.
Example:
When a user types “0103”, their cursor is at position 4. After formatting it into “01/03”, the cursor should still feel like it’s at the 4th numeric character—even though the slashes shifted everything around it.
It’s like trying to remember where you left your keys after cleaning your entire house. The keys are still somewhere… but the “house” (string) has changed.
You need a system.
The three functions that got me through it
1) reformatDatePickerInput — The traffic controller
This is the main entry point. It:
- Captures the current input value and cursor position
- Counts numeric characters before the cursor (the real trick)
- Strips everything down to numbers and caps it at 8 digits
- Formats the date with slashes
- Restores the cursor to the correct place
The key insight:
Count numeric characters. Not total characters.
If the user typed "01/03" and the cursor is after the “3,” they’ve typed 4 numerics—not “position 5.”
2) buildDatePickerInput — The formatter
This one just takes "01031990" and returns "01/03/1990".
It’s the part everyone thinks is hard.
Spoiler: It’s not.
3) calculateNewCursorPosition — The GPS of your typing experience
This is where the magic happens.
It:
- Walks through the formatted string
- Counts numeric characters
- Finds where the cursor should land
- Skips over slashes so the cursor never sits on formatting characters
Because nobody wants the cursor parked on a slash.
That’s like parking your car on the far side of the parking lot—you’re technically there, but you know you could do better.
Try it out yourself!
I've created an interactive demo where you can test this formatter in real-time:
Real-world example: the form that made me write this
I was building a registration form where users needed to enter their date of birth.
The backend demanded MM/DD/YYYY.
No exceptions.
Users (being human) typed things like:
- "01031990" (no slashes, raw efficiency)
- "1/3/1990" (no leading zeros, even more efficiency)
- "01-03-1990" (dashes instead of slashes, muscle memory from other forms)
So the solution had to:
- Accept any of these
- Normalize them consistently
- Maintain cursor position
- Not make the user want to throw their laptop
The result? ~80 lines of code that make date input less painful.

The reliability mindset
A reliable input field doesn’t promise to never need formatting.
Instead, it promises that we’ve anticipated real user behavior, handled it gracefully, and prevented formatting from destroying the experience.
Same principles you’d apply anywhere else:
- Fault tolerance: users will absolutely type weird things
- Predictability: same input, same output (and cursor!)
- Graceful degradation: when in doubt, don’t break everything
Building for cursor position tolerance
If users typing chaotic date formats is inevitable (it is), then build around that.
- Track numeric characters, not total characters
- Account for insertions (slashes shift positions)
- Check bounds (no cursor at position 47 in a 10-char string)
- Test edge cases (typing, deleting, pasting, clicking, sighing, etc.)
These are the things that keep you up at night… and also the things that make your code actually work (for now).
But what do I know?
I’m just a guy who likes to build. Different apps have different needs, and there’s no universal “right” date input format.
Though, deep in my heart, I know that we can agree on this:
The best input fields are the ones you never notice.
— Carl Eubanks, Looking Left Doesn’t Mean You’ve Looked Right (2019)