Now that we’ve built a solid foundation for our form inputs, let’s explore some advanced styling techniques that can elevate the user experience even further. We’ll build on the input component we created, adding polish and interactivity while maintaining accessibility.
Enhancing Focus States with Rings
In our previous tutorial, we used outlines for focus states. Let’s enhance this approach by combining outlines with ring utilities for even more prominent focus indicators.
<div>
<label for="username" class="block text-sm/6 font-medium text-slate-900">Username</label>
<div class="mt-2">
<input
type="text"
name="username"
id="username"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 focus:ring-4 focus:ring-indigo-600/40 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6"
placeholder="johndoe"
/>
</div>
</div>
The key addition here is focus:ring-4 focus:ring-indigo-600/40
:
focus:ring-4
: Creates a 4px ring around the input when focusedfocus:ring-indigo-600/40
: Uses the same indigo color as our outline, but with 10% opacity
This creates a subtle glow effect that extends beyond the outline, making the focus state even more prominent while maintaining visual cohesion.
Styling Placeholder Text
Default placeholder text can feel disconnected from your design system. Let’s make it more intentional:
<input
type="text"
name="username"
id="username"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 placeholder:italic focus:ring-4 focus:ring-indigo-600/40 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6"
placeholder="Enter your username..."
/>
We’ve enhanced the placeholder with:
placeholder:text-slate-400
: Light gray color that doesn’t compete with actual input textplaceholder:italic
: Subtle italic styling to differentiate placeholder from real content
The italic styling helps users distinguish between placeholder text and actual input values, which is particularly helpful for users with cognitive disabilities.
Customizing the Text Cursor
The default text cursor (caret) can be styled to match your brand colors:
<input
type="text"
name="username"
id="username"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 caret-indigo-600 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 placeholder:italic focus:ring-4 focus:ring-indigo-600/40 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6"
placeholder="Enter your username..."
/>
The caret-indigo-600
class changes the blinking text cursor to match our focus color, creating a cohesive experience when users are actively typing.
Validation States
Real-world forms need to communicate validation status clearly. Let’s add support for valid and invalid states:
<!-- Invalid state -->
<div>
<label for="email" class="block text-sm/6 font-medium text-slate-900">Email</label>
<div class="mt-2">
<input
type="email"
name="email"
id="email"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 caret-indigo-600 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 placeholder:italic invalid:text-red-900 invalid:outline-red-500 invalid:placeholder:text-red-300 focus:ring-4 focus:ring-indigo-600/40 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6"
placeholder="Enter your email..."
value="invalid-email"
/>
</div>
</div>
Invalid state styling:
invalid:outline-red-500
: Red outline when HTML5 validation failsinvalid:text-red-900
: Dark red text for error stateinvalid:placeholder:text-red-300
: Lighter red for placeholder in error state
This leverages HTML5’s built-in validation, automatically applying error styles when the input doesn’t match the expected pattern.
Valid State Feedback
For positive reinforcement, we can also style valid inputs:
<input
type="email"
name="email"
id="email"
required
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 caret-indigo-600 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 placeholder:italic valid:outline-green-500 invalid:text-red-900 invalid:outline-red-500 invalid:placeholder:text-red-300 focus:ring-4 focus:ring-indigo-600/40 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6"
placeholder="Enter your email..."
value="user@example.com"
/>
The valid:outline-green-500
provides immediate positive feedback when users enter valid data, encouraging continued engagement.
Disabled States and Cursor Feedback
Disabled inputs need clear visual indicators and appropriate cursor behavior:
<input
type="text"
name="readonly-field"
id="readonly-field"
disabled
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 caret-indigo-600 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 placeholder:italic disabled:cursor-not-allowed disabled:bg-slate-50 disabled:text-slate-500 disabled:outline-slate-200 sm:text-sm/6"
placeholder="This field is disabled"
/>
Disabled state styling:
disabled:bg-slate-50
: Light gray background indicates unavailabilitydisabled:text-slate-500
: Muted text colordisabled:outline-slate-200
: Very light outline for minimal visual weightdisabled:cursor-not-allowed
: Shows the “not allowed” cursor on hover
Checkbox and Radio Button Accents
Form controls like checkboxes and radio buttons can also match your design system:
<div class="flex items-center gap-2">
<input type="checkbox" id="newsletter" name="newsletter" class="h-4 w-4 accent-pink-500" />
<label for="newsletter" class="text-sm text-slate-900">Sign Up for Spam</label>
</div>
<div class="flex items-center gap-2">
<input type="radio" id="option1" name="choice" value="option1" class="h-4 w-4 accent-pink-500" />
<label for="option1" class="text-sm text-slate-900">The Only Option</label>
</div>
The accent-indigo-600
class styles the active/checked state of form controls to match your brand colors. The h-4 w-4
classes ensure consistent sizing across different browsers.
Complete Enhanced Input Example
Here’s our fully enhanced input with all improvements:
<div>
<label for="enhanced-input" class="block text-sm/6 font-medium text-slate-900">
Enhanced Input
</label>
<div class="mt-2">
<input
type="email"
name="enhanced-input"
id="enhanced-input"
required
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-slate-900 caret-indigo-600 outline-1 -outline-offset-1 outline-slate-300 placeholder:text-slate-400 placeholder:italic valid:outline-green-500 invalid:text-red-900 invalid:outline-red-500 invalid:placeholder:text-red-300 focus:ring-4 focus:ring-indigo-600/40 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 disabled:cursor-not-allowed disabled:bg-slate-50 disabled:text-slate-500 disabled:outline-slate-200 sm:text-sm/6"
placeholder="Enter your email address..."
/>
</div>
</div>
Performance Considerations
These styling enhancements add minimal overhead since they leverage CSS pseudo-classes and don’t require JavaScript. The :invalid
and :valid
pseudo-classes provide functionality that would otherwise require custom validation scripts.