CSS Tutorial / CSS Form States: Focus, Validation, disabled, and readonly
CSS can style focus, validity, and disabled or read-only form controls, but the HTML defines their labels, constraints, and submission behavior. :invalid can match before interaction, while :user-invalid helps delay error styling until the browser considers it appropriate.
Associate labels and instructions with controls
If the for of label and the ID of the input window are specified as the same, the item name and the input window are connected. Placeholders disappear once you start typing, so they don't take the place of names or important conditions. For additional explanations, leave the sentence visible and specify the ID of the sentence in aria-describedby.
<label for="email">Email address (required)</label> <input id="email" name="email" type="email" required placeholder="name@example.com" aria-describedby="email-help"> <p id="email-help">Enter name@example.com in the same format.</p>
The partial code above briefly represents the email section of the full example below. id identifies the object in the document, and name is the item name in the submitted data. If name is not present, it will not be included in normal submissions, even if you can type it on screen. For the composition of labels and descriptions, refer to W3C form label guide and form input guide.
Size controls for narrow screens and larger text
If you only rely on the inherent width of the input window, it may go over a narrow screen when the font size is increased. Set the maximum width of the form and use width: 100% and box-sizing: border-box together in the input window. This will ensure that the border and padding are also included within the specified width.
*, *::before, *::after { box-sizing: border-box; }
form { max-width: 28rem; }
input { width: 100%; min-width: 0; padding: .65rem; font: inherit; }
This rule is included in the style section of the entire example. font: inherit allows the input window and buttons to follow the font, size, and line height of the text, and min-width helps reduce the minimum size that prevents reduction even when inserting the input window into a Flexbox/Grid. A fixed height is not used so that the items below naturally move down even when the description is increased or the letters are enlarged.
Separate focus state and validation errors
:focus matches the element that currently has focus, and :focus-visible matches if the browser determines that it needs to display focus. It is mainly displayed during keyboard navigation, but it can also be displayed by clicking with the mouse in the text input window, so it should not be understood as “applies only to the keyboard.” MDN focus-visible description The judgment method is provided in. To apply focus indication consistently to links as well, compare CSS Link States and Visible Keyboard Focus .
Focus indicates the item you are currently manipulating, and errors indicate items that did not meet the input conditions. Since both states can be present at the same time, the entire example shows the focus with a blue outline and the error with a dark red border and an error statement. Leave some room around the outline to avoid truncating it, and avoid removing the outline and not providing another focus indicator.
:invalid matches if the current value is invalid, so an empty required input field is also matched from the beginning. :user-invalid matches when the browser will display an error condition following user interaction. When you display something, such as when you finish typing or when you attempt to submit, may depend on your browser's behavior. The lab checks for errors by pressing the Submit button. MDN user-invalid Description See.
If you need to consider older support targets, you must check whether the selector is supported and design a separate verification flow. Even if error decoration does not appear because the selector is not supported, this does not mean that HTML's required and formatting checks disappear. Choose a focus color that is sufficiently distinct from the actual background, and also check the application conditions of W3C Non-Text Contrast Guide.
Compare required, valid, disabled, and readonly
| situation | meaning | Focus and general submission |
|---|---|---|
required |
User must enter a value | Focusable if active item; Empty values are subject to validation upon submission |
:valid·:invalid |
Whether HTML validation conditions are met | Verification results are determined independently from the border shape |
disabled |
Currently unavailable | Typically excluded from tab navigation and submissions |
readonly |
Cannot modify values such as text input supported | Focusable and selectable; If name exists, include it in submission |
readonly is supported by particular text-like controls and is not a general way to make a select element read-only. Supported read-only controls are excluded from constraint validation, while named controls can still enter submitted form data. Distinguish that behavior from disabled controls. See HTML Standard: constructing the entry list, MDN: readonly.
This email field demonstrates a format such as name@example.com. Passing its validity check does not confirm that the account exists or can receive mail. HTML type="email" validation does not accept every internationalized address format; non-ASCII local parts are not presented here as guaranteed valid input. For further detail, see HTML standard email input conditions.
Run the example and inspect submitted fields
Save the following code as form-states.html in UTF-8 and open it in your browser. HTML and CSS determine the state of the form. The short piece of JavaScript at the end is auxiliary code to intercept valid submissions and display a list of data on the screen, without sending it to the server or performing any extra input checking. If JavaScript is turned off, the result output function will not work.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Comparing form states and submitted fields</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
form { max-width: 28rem; }
.field { margin-block: 1.25rem; }
label { display: block; margin-bottom: .35rem; font-weight: 700; }
input { display: block; width: 100%; min-width: 0; padding: .65rem;
border: 2px solid #6d7e8b; border-radius: .3rem; background: white; color: #183044; }
input:required { border-inline-start-width: 4px; }
input:user-invalid { border-color: #9d2632; }
.help, .error { margin-block: .4rem 0; }
.error { display: none; color: #9d2632; }
input:user-invalid + .error { display: block; }
input[readonly] { background: #eef6fa; }
input:disabled { background: #eceff2; color: #465766; cursor: not-allowed; }
button { padding: .65rem 1rem; border: 1px solid #164f73; border-radius: .3rem;
background: #166b92; color: white; }
output { display: block; margin-block: 1rem; white-space: pre-wrap; overflow-wrap: anywhere; }
</style>
</head>
<body>
<main>
<h1>Check input states and submitted data</h1>
<form id="demo-form">
<div class="field">
<label for="email">Email address (required)</label>
<input id="email" name="email" type="email" autocomplete="email" required
placeholder="name@example.com" aria-describedby="email-help email-error">
<p id="email-error" class="error">Enter an email address in a format such as name@example.com; do not leave the field empty.</p>
<p id="email-help" class="help">This sample input checks the address format. It does not check whether the example address receives mail.</p>
</div>
<div class="field">
<label for="plan">Plan (read-only)</label>
<input id="plan" name="plan" type="text" value="Basic" readonly>
</div>
<div class="field">
<label for="coupon">Coupon (currently disabled)</label>
<input id="coupon" name="coupon" type="text" value="WELCOME" disabled>
</div>
<button type="submit">Check submitted fields</button>
</form>
<output id="result" for="email plan coupon" aria-live="polite"></output>
<script>
const form = document.getElementById("demo-form");
form.addEventListener("submit", (event) => {
event.preventDefault();
const entries = Array.from(new FormData(form).entries());
document.getElementById("result").textContent = JSON.stringify(entries, null, 2);
});
</script>
</main>
</body>
</html>
- Leave your email blank and tap Confirm Submission. Check your browser for required input instructions and error messages.
- Enter
abcin the email and resubmit. A new result list should not be output due to a format error. - Enter
name@example.comand submit. The results include email and plan, but there is no disabled coupon. - Use the Tab key to navigate to the Email → Plan → Submit button. Inactive coupons are skipped out of the normal tab order, and read-only plans can receive focus.
- In the entire example, we only remove the disabled attribute of the coupon. You can focus on coupons and add value to your submission list. After confirmation, restore disabled.
- Remove only the plan's
readonlyattribute and try editing it. After the comparison, restore its value to “Basic” and addreadonlyagain. - Reduce the screen width and adjust browser enlargement or font size settings. Check that the input window, focus outline, and error sentences are not cut off or overlapped.
Understand the limits of CSS and browser validation
CSS is responsible for showing the state of an item. Browser verification can reduce user input mistakes, but it can be bypassed with developer tools or separate requests, so the actual service must recheck the allowable values, length, permissions, etc. on the server. Error sentences specifically describe problematic items and solutions, and a separate flow to preserve input information is also designed when a server error occurs.
If the example behaves unexpectedly, first check label for/id pairs, submission names, required, input types, and disabled or readonly states. If focus is invisible, check whether another outline rule overrides it. Errors shown immediately may come from :invalid. If the message stays hidden after a submission attempt, inspect :user-invalid support and the CSS Combinators and Pseudo-Classes.










