If you’re a web developer like me, you’ve probably styled a form before.
And if you’re anything like me, you’ve tried to be helpful — maybe you added some color changes with input:valid
or input:invalid
, outlined fields, maybe even threw in a little transition effect.
But then this came to your mind: Wait, why is this field showing up as valid before the user even typed anything? 🤦
Want smart web dev tips like this delivered straight to your inbox?
JavaScript, CSS, HTML tricks and tools that actually make your life easier.
→ Follow 300+ webdevs, join the newsletter and get your CSS selector cheat sheet for free
The problem
You try to be nice to users. Give them visual cues. Maybe you change the border color on a valid input, or show a message when they make a mistake.
You think you’re doing a good thing.
But then someone opens your form and sees green borders before they’ve even clicked a thing.
Valid already? They didn’t even type! It’s wrong. It feels broken. And worse: it actually is misleading.
Let’s not even talk about accessibility. Using just colors for feedback? That’s a hard “no” if you care about users with color blindness.
Here’s the deal
CSS pseudo-classes like :valid
and :invalid
don’t wait for the user to interact.
If the input field meets the required rules on page load (or fails them), the styles apply instantly.
That’s why your untouched email field might already look like a success story. Not cool.
But the real solution? It’s brand new:
Say hello to
:user-valid
and:user-invalid.
These two pseudo-classes work exactly how you always wanted :valid
and :invalid
to behave:
*They wait for user interaction.
*
Nothing changes until the user actually types, blurs, or otherwise engages with the input. Now that’s user-friendly feedback.
What It Means For You
If you’re still using :valid/:invalid
on untouched fields, you're frustrating users. They’re seeing red borders before they’ve even typed anything. Or worse—green borders that falsely imply their input is good.
By switching to :user-valid
and :user-invalid
, your forms become:
...
Top comments (4)
While
user-valid
anduser-invalid
are welcome additions, I would not use them.For example test@domain triggers
user-valid
when the input type is set to email.Absolutely—thanks for bringing that up!
You're totally right: user-valid still relies on the browser’s built-in validation logic, and yeah, it’s far from perfect. The classic test@domain issue is a great example—technically valid, but practically useless. It's like the form equivalent of a "close enough" answer in a math test.
I see :user-valid less as a silver bullet and more as a UX upgrade over :valid. It doesn’t fix the validation rules themselves—it just delays the feedback until users actually interact, which feels more human.
That said, pairing it with custom validation or using pattern can help, but it’s not foolproof.
Curious—how do you handle validation when native types fall short? Regex? JS? Libraries like Zod or Yup?
If the validation rules are simple the
pattern
attribute can be a solution. Once the rules can't be handled by html features a JS solution has to be used.If you want to go extra fancy you can use a serverless functions that is aware of the backend validation, but that is overkill in most cases.
Love that take—completely agree.
The pattern attribute is underrated when your rules are just a bit beyond the basics but still manageable in HTML. It’s a great middle ground—especially when you want to keep things lightweight and avoid pulling in JS for everything.
And yeah, serverless validation that mirrors backend logic is cool in theory (and super slick in demos), but in real-world apps? Total overengineering 95% of the time. You end up chasing sync issues between frontend and backend anyway.
That said, I’m seeing more teams leaning into shared schema validation (like Zod + tRPC or Joi + Express) to handle that "one source of truth" dream. Ever tried something like that, or are you more into keeping frontend and backend concerns separate?