Disabled controls, ARIA states, and forms
State variants can reflect native HTML states, for example disabled:opacity-50, disabled:cursor-not-allowed, invalid:border-red-500, and checked:bg-blue-600. Prefer native controls and native states where possible. An aria-* attribute communicates meaning to assistive technology; Tailwind can style matching attributes, but the attribute itself must be set correctly by the application.
A disabled button should actually have the disabled attribute when the action cannot be used. Styling a button to look disabled without disabling it creates a misleading interface.
A red border alone may not explain the problem. Add clear error text, connect it with aria-describedby, and validate on the server as well as the client.
<label class="block">
<span class="text-sm font-medium text-slate-800">Work email</span>
<input type="email" required aria-describedby="email-help"
class="mt-2 w-full rounded-lg border border-slate-300 px-3 py-2.5 outline-none focus-visible:ring-2 focus-visible:ring-blue-600 invalid:border-red-500"
placeholder="you@example.com">
<span id="email-help" class="mt-1 block text-sm text-slate-600">Use your company email address.</span>
</label>
<button disabled class="mt-4 rounded-lg bg-blue-600 px-4 py-2.5 font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50">
Submit request
</button>required constraint. The invalid border reflects browser validation state, and the focus ring makes keyboard focus visible. In a production form, explain validation errors in text and associate them with the relevant input.