This is the boiler code we will be working with. For this demo, the input have the minLength
and required
attributes indicating the user must enter a value and the minimum input length must be at least 3.
This code has no validation
export default function Example() { return ( <div className="p-4 w-full h-screen "> <label htmlFor="name" className="text-sm font medium"> First Name </label> <input required minLength={3} type="text" id="name" className="mt-1 w-full rounded-md text-sm border-gray-300 border shadow-sm h-8 px-2" /> </div> ) }
valid and invalid modifiers
We can add the valid:
and invalid:
modifiers to the input
class to change the border color based on the user's input.
Make the border green if the input is valid and red if it's invalid
export default function Example() { return ( <div className="p-4 w-full h-screen "> <label htmlFor="name" className="text-sm font medium"> First Name </label> <input required minLength={3} type="text" id="name" className="valid:border-green-500 invalid:border-red-500 mt-1 w-full rounded-md text-sm border-gray-300 border shadow-sm h-8 px-2" /> </div> ) }
We have a problem now. Because the input is empty, it is considered invalid. So what can we do?
:user-valid and :user-invalid modifiers
:user-valid
and :user-invalid
are not out yet. Use arbitrary properties to achieve the same effect.We can replace valid:
and invalid:
with [&user-valid]:
and [&user-invalid]:
respectively.
Unlike valid:
and invalid:
, [&user-valid]:
and [&user-invalid]:
only matches once the user has interacted with it.
export default function Example() { return ( <div className="p-4 w-full h-screen "> <label htmlFor="name" className="text-sm font medium"> First Name </label> <input required minLength={3} type="text" id="name" className="[&:user-valid]:border-green-500 [&:user-invalid]:border-red-500 mt-1 w-full rounded-md text-sm border-gray-300 border shadow-sm h-8 px-2" /> <p className="hidden text-red-500 text-sm"> This field is required </p> </div> ) }
This is good enough for some use cases but we can make it even better.
peer modifier
We can add some text underneath the input
when it's invalid and hide it when it's valid.
This can be achieve using the peer
modifier.
export default function Example() { return ( <div className="p-4 w-full h-screen "> <label htmlFor="name" className=" text-sm font medium"> First Name </label> <input required minLength={3} type="text" id="name" className="peer [&:user-valid]:border-green-500 [&:user-invalid]:border-red-500 mt-1 w-full rounded-md text-sm border-gray-300 border shadow-sm h-8 px-2" /> <p className="peer-invalid:block hidden text-red-500 text-sm mt-1"> This field is required </p> </div> ) }
peer
allows us to style an element based on the state of a sibling element. In this example, we marked the sibling input
with the peer
class
and the targeted element p
with peer-invalid:
But now we run into the same issue as before when using invalid.
Let's replace invalid:
with [&user-invalid]:
export default function Example() { return ( <div className="p-4 w-full h-screen "> <label htmlFor="name" className=" text-sm font medium"> First Name </label> <input required minLength={3} required type="text" id="name" className="peer [&:user-valid]:border-green-500 [&:user-invalid]:border-red-500 mt-1 w-full rounded-md text-sm border-gray-300 border shadow-sm h-8 px-2" /> <p className="peer-[&:user-invalid]:block hidden text-red-500 text-sm mt-1"> This field is required </p> </div> ) }