How do you select a text input in CSS?
Write input[type="text"], or add a class to the field and select that. The attribute version catches every single-line text field on the page. A class only touches the ones you tag.
The WHATWG HTML Living Standard describes the Text state as a one-line plain text edit control that strips line feeds from its value. Email, search, tel and url fields share the same box and font properties, so one grouped selector covers them.
input:where(:not([type]), [type="text"], [type="email"], [type="search"], [type="tel"], [type="url"]) {
font: inherit;
}
The :not([type]) part matters. An input with no type attribute falls back to the Text state but never matches input[type="text"].
Author rules always beat the user agent stylesheet, which gives every input its default border, padding and font. Even a bare type selector overrides those defaults.
Counted as ID-CLASS-TYPE weights (MDN), a bare input is 0-0-1 and .field is 0-1-0. input[type="text"] comes out at 0-1-1.
That means the attribute selector outranks the class, so a .field override of the same property loses. It also ties with input:focus at 0-1-1, and the later rule wins a tie. This is how a base rule written below the focus rule erases the focus style, and it’s an annoying bug to track down.
A specificity calculator settles these disputes in seconds. Wrapping the base selector in :where() drops its weight to zero, so any later class overrides it cleanly. I’d do that on any shared stylesheet.
Box model settings that size a text input
A text input uses content-box by default, so width covers the content area only and padding and border are added outside it (MDN). Set width: 100% with 12px of side padding and a 1px border, and the field overflows its parent by 26px.
.field {
box-sizing: border-box;
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid #767676;
border-radius: 6px;
}
Under border-box the width is the total, padding and border included, which is why I set it on every field. Padding is the gap between the border and the typed text. The border is the field boundary, and on most designs it’s the only visible edge. border-radius rounds the corners.
Corner values are easy to judge by eye, but a border radius generator gives a live preview when you want a pill shape or a single rounded corner.
For an underline-only field, set border: 0 and then add border-bottom on its own.
A restyled border carries a contrast requirement. WCAG 2.2 (W3C, 2023) asks for 3:1 against adjacent colors on visual information needed to identify a control, and the exemption for browser-default appearance ends once the author modifies it.
By the WCAG formula, #ccc on white lands near 1.6:1, #949494 near 3:1 and #767676 near 4.5:1. That’s why the border in the example above is #767676. Check color contrast against the real page background, because a gray card behind the field changes the result.
Font inheritance and the 16px rule on iOS
A text input ignores the page font until you set font: inherit, and Safari on iOS zooms the page when a focused field renders below 16px.
MDN’s form styling guide notes that some widgets do not inherit font-family and font-size from their parents. Firefox’s user agent stylesheet (forms.css) gives inputs the operating system’s textfield font through font: -moz-field.
input,
textarea,
select {
font: inherit;
font-size: max(1rem, 16px);
}
Setting the field to 16px or larger stops the zoom everywhere, and it stays flexible if you write it in rem. Capping zoom in the viewport meta tag also stops the jump, but it changes zoom behavior for the whole page.
Telerik’s documentation calls the zoom intended behavior, and the West Wind Web Log (2023) ties it to a rendered text size under 16px. Neither source describes it as a bug.
Rem units can hide the problem. Rem units resolve against the root size, so a 62.5% root reset makes 1rem equal 10px and pushes a 1rem input far under the threshold (a root below 16px is one of the causes West Wind lists).
Skip the viewport cap. West Wind lists it only as a selective workaround for Safari on iOS, and a page-wide viewport setting is a heavy tool for a one-control problem.
Which pseudo-classes style the states of a text input?
A text input mostly needs :hover, :focus, :disabled, :read-only and :invalid. :user-invalid (CSS Selectors Level 4) is the better pick over :invalid when errors should appear only after the user acts.
| Pseudo-class | Matches when | Typical style |
|---|
:hover | Pointer sits over the field | Darker border color |
:disabled | The disabled attribute is set | Muted background, not-allowed cursor |
:read-only | The readonly attribute is set | Flat background, normal text color |
:invalid | Value fails its constraints, empty required fields included | Error border color |
:user-invalid | Value is invalid after the user interacted | Error border plus message |
Store state colors in custom properties such as --field-border and --field-border-error, then swap the variable per state instead of rewriting declarations. A short transition: border-color 150ms ease softens each change.
Disabled and read-only fields
Disabled fields get a pass on contrast. WCAG 1.4.11 exempts inactive components (W3C, 2023), so a muted gray is acceptable there.
Readonly fields don’t get that treatment, since they stay focusable and readable. Their text and boundary keep full contrast. Readonly values still submit with the form, which disabled ones never do.
Older compatibility write-ups from CSS-Tricks and Codrops report disabled inputs matching :read-write in major browsers, against the specification. Write input:read-only:not(:disabled) so the rule does not depend on how a browser classifies disabled fields.
Validation states
:invalid matches an empty required field the moment the page loads. Error borders can appear before anyone types a character.
:user-invalid waits for interaction or a submit attempt, which is the behavior most forms want. The web-features dataset lists support in Chrome 119, Firefox 88 and Safari 16.5, with Baseline status since November 2023 and Widely available status since May 2026.
Older Firefox versions added a red glow through box-shadow on invalid fields, but Firefox 88 removed that default style (Mozilla bug 1693969). Current browsers draw no error ring of their own, so the error style is entirely yours.
Pair the red border with visible error text, because color alone does not tell every user what went wrong. Accessible form patterns cover label association and message placement in more depth.
How to style the focus indicator without failing WCAG
Replace the default focus outline with a custom indicator that holds at least 3:1 contrast against its surroundings, and never remove it without a replacement.
SC 1.4.11 in WCAG 2.2 (W3C, 2023) sets that bar at 3:1 for a control’s boundary and states against adjacent colors, at Level AA. SC 2.4.13 goes further at Level AAA and asks for a minimum indicator area equal to a perimeter 2 CSS pixels thick, with a 3:1 contrast change between focused and unfocused pixels. SC 2.4.11, also Level AA, says the focused field can’t be entirely hidden by sticky headers or pop-ups.
On the browser side, :focus-visible became available in all major browsers in March 2022, and MDN lists it as Baseline Widely available (as of October 2026).
For text inputs, :focus and :focus-visible behave alike, because browsers show focus on a field after a mouse click too (web.dev). Use :focus-visible anyway, since it matches how the browser’s own heuristics work.
.field:focus-visible {
outline: 3px solid transparent;
outline-offset: 2px;
border-color: #1a56db;
box-shadow: 0 0 0 3px #1a56db;
}
The transparent outline looks invisible in normal rendering. In forced colors mode MDN notes that box-shadow is forced to none, and the outline is what stays visible in a system color.
GOV.UK Frontend added the same transparent-outline technique to the focus states of its radio and checkbox controls for Windows High Contrast Mode (pull request 2264). GOV.UK also pairs yellow with black and a thick bottom border so its focus states meet SC 1.4.11 on any background, and the NHS service manual describes its text input version as a yellow outline with a thick black border.
A box-shadow generator helps tune spread and color of the ring. The shadow handles the look, and outline-offset keeps the invisible outline from touching the border.
This setup still breaks in a few places. A pale brand-blue ring on white falls under 3:1, and a shadow-only ring vanishes in forced colors mode. A sticky header that fully covers the focused field fails SC 2.4.11 as well, though partial overlap does not (Vispero).
MDN’s :focus reference states the rule plainly: removing the outline is acceptable only when the replacement passes SC 1.4.11.
How to style placeholder text
Target it with input::placeholder, set color and opacity explicitly, and use :placeholder-shown when the field itself needs a different look while the hint is visible.
The pseudo-element accepts only the property subset that applies to ::first-line, according to MDN. Color, font properties and opacity work. Padding and border do not.
input::placeholder {
color: #595959;
opacity: 1;
}
Browsers disagree on the default. Chrome paints placeholder text #757575 (Chromium html.css), while Firefox uses the input’s own color at 54% opacity (MDN), so the same stylesheet looks lighter in Firefox.
That default has a contrast cost. By the WCAG formula, #333 text at 54% opacity over white lands near #919191, about 3.2:1, under the 4.5:1 that SC 1.4.3 asks for normal-size text.
MDN also warns about the opposite problem. Placeholder text with strong contrast can be mistaken for an entered value, and the hint vanishes the moment someone types.
My rule is to keep the placeholder to a short example, put instructions in a visible hint or label, and never let it replace the <label> element.
::placeholder styles the hint text itself, while :placeholder-shown styles the input for as long as the hint displays. Both have been available in every major browser since January 2020 and are Baseline Widely available (MDN, as of October 2026). Older tutorials still teach ::-webkit-input-placeholder and :-moz-placeholder, which the unprefixed selector made unnecessary.
The pseudo-class earns its place on narrow fields. text-overflow: ellipsis on the shown state trims a long hint that would otherwise be cropped mid-word.
In forced colors mode (Windows High Contrast Mode), placeholder text renders with the same styling as typed text, per MDN. Any gray you chose is dropped there.
Caret color and autofill styling
caret-color recolors the blinking insertion pointer, and :autofill targets fields the browser filled in, though Chrome locks the autofill background and text color behind !important.
The caret property affects only the insertion caret, not the mouse cursor, and its default auto resolves to the text color (MDN). It has been Baseline Widely available since July 2022, with Chrome 57, Firefox 53 and Safari 11.1 as the first supporting versions (web-features dataset).
.field {
caret-color: #1a56db;
}
The value auto is not animatable, so a transition from auto to a color snaps instead of fading. Set the caret color on the base rule if a state change touches it.
For autofill, match both :-webkit-autofill (the prefix from WebKit) and :autofill inside :is(), so one unknown selector cannot void the rule. Chrome 110 (February 2023) and Safari 15 support the standard name, but it is not Baseline: Firefox matches only autofilled username and password fields (MDN browser compatibility data). Either selector stops matching once the user edits the field (MDN).
Borders, outlines and radii on autofilled fields behave normally. Background and text color do not, at least on the selected autofill state, because Chrome’s internal stylesheet sets background-color (rgb(232 240 254) in light mode), background-image and color with !important, which MDN documents on its :autofill page.
The workaround is an oversized inset box-shadow that paints over the locked background, plus -webkit-text-fill-color to replace the locked text color, a technique CSS-Tricks documents. It exploits paint order, not a specification, so a Chrome stylesheet update can break it.
input:is(:-webkit-autofill, :autofill) {
-webkit-text-fill-color: #111;
box-shadow: 0 0 0 1000px #fff inset;
}
input:is(:-webkit-autofill, :autofill):focus-visible {
box-shadow: 0 0 0 1000px #fff inset, 0 0 0 3px #1a56db;
}
The trick spends the same property that a shadow-based focus ring uses. The second rule repeats the fill next to the ring because an element gets only one box-shadow value. On focus, .field:focus-visible (0-2-0) outranks the first autofill rule (0-1-1) and would replace the fill, bringing Chrome’s background back. The second rule (0-2-1) outranks both and carries the fill and the ring together.
The workaround has costs. A fixed fill color hides the cue that autofill happened, a trade-off raised in a BS Base theme issue on Drupal.org (3256665) when it reset the color. Chrome also changes font size during autofill selection with !important rules, and that cannot be overridden (same issue). WebKit bug 66032, which asks for author control of autofill colors, is still the reference MDN points to.
How Chrome, Firefox and Safari render a default text input
Each browser engine ships its own user agent stylesheet, so an unstyled text input differs across Chrome, Firefox and Safari in padding, font, placeholder color and autofill selector.
| Property | Chrome | Firefox |
|---|
| Padding and border | 1px block and 2px inline padding, 2px inset border (Chromium html.css) | 1px block and 2px inline padding (forms.css) |
| Font | Platform default font (macOS, per MDN) | OS textfield font via font: -moz-field |
| Placeholder color | #757575 | Input color at 54% opacity |
| Autofill selector | :-webkit-autofill | :autofill |
The values live in separate files, Chromium’s html.css and Firefox’s forms.css (Searchfox), which is why no single reset value fits every engine. Safari follows WebKit’s own stylesheet, and that lineage is why the -webkit-autofill prefix still works in Chrome and Safari.
Stylesheets change between releases, so treat the table as a starting point for cross-browser compatibility checks. In Chrome DevTools, the Styles pane labels inherited rules as the “user agent stylesheet” and the Computed pane shows the final resolved value (Chrome for Developers).
appearance: none strips native styling and keeps the control’s behavior (MDN). The unprefixed property reached Chrome 84, Firefox 80 and Safari 15.4, and the web-features dataset lists Baseline Widely available since September 2024.
Normalize.css version 8.0.1 keeps useful defaults instead of wiping them, and its changelog lists form inputs inheriting font-family and placeholders normalized in Chrome, Edge and Safari.
Its README still lists Internet Explorer 10+ and Safari 8+ as supported browsers, so much of it solves problems a current project does not have. For one text input, a short reset beats the import: font: inherit, box-sizing: border-box and an explicit placeholder color.
Can I use and MDN’s compatibility tables show per-version support for each selector covered here. Check them before depending on anything newer than the Baseline dates above.
Plain CSS or a framework for text inputs?
Write plain CSS for one or two forms, and use Bootstrap or Tailwind CSS when the project already runs on one.
Plain CSS means every rule is yours and there’s no stock focus ring to fight. You also own every cross-browser reset, which gets tedious past a couple of forms.
Bootstrap’s .form-control brings padding of .375rem .75rem and a .15s border and shadow transition, with focus styling driven by variables (Bootstrap 5.3 documentation). The catch is the default focus ring, a 0.25rem shadow drawn at 0.25 opacity in the 5.3 build, so test it against your contrast target.
The Bootstrap input styling classes cover sizes through .form-control-sm and .form-control-lg, and overriding them follows the same specificity rules as any other selector.
With Tailwind CSS, @tailwindcss/forms resets inputs with :where() selectors, so utilities override it without a specificity fight. Version 4 targets Safari 16.4+, Chrome 111+ and Firefox 128+ and will not work in older browsers (Tailwind CSS v4 upgrade guide).
Tailwind’s own defaults changed in early 2025 and hit text inputs directly. Version 4 moved the default border color from gray-200 to currentColor and the default ring width from 3px to 1px, so a migrated input that relied on the old defaults now draws a border in its text color and a thinner ring.
Pairing styling inputs with Tailwind utility classes with explicit border and ring colors avoids that surprise. The forms plugin also offers a class strategy, which skips global styling and applies form-input only where you add it.
CSS Input Text FAQ
Do password and number inputs take the same CSS as a text input?
Yes for the shared properties. Padding, border, border-radius, font and the state pseudo-classes all apply.
Number fields add spin buttons and search fields add a clear button, so both need pseudo-element rules of their own.
How do you build a floating label with CSS?
Position the <label> over the field, then move and shrink it when the input has focus or holds text. The holds-text check uses input:not(:placeholder-shown), which needs a placeholder attribute, even a single space, because the pseudo-class matches only while a placeholder displays.
Keep the label a real <label> element. The placeholder is a trigger, not a replacement.
How do you put an icon inside a text input?
Wrap the input in a container set to position: relative and place the icon absolutely inside it. Then add padding on the icon’s side of the input so typed text never runs underneath.
A background-image on the input works for a static icon, with the same padding fix.
How do you style a text input for dark mode?
Set color-scheme: light dark on the root so the browser swaps its own field colors, then override with custom properties inside @media (prefers-color-scheme: dark). Chromium’s default stylesheet paints input background and text with the Field and FieldText system colors, and that property drives them.
Recheck the border and focus indicator on the dark surface. A ring that passes on white can fail on dark gray.
Can a CSS generator write the whole text input style?
No. A generator such as CSS Portal’s text input generator returns CSS plus a preview of one styled field.
Test the focus, disabled and invalid states yourself, because a preview shows only what you configured.
Setting Up a Shared Base Stylesheet for Text Inputs
Sizing and font go into a shared base stylesheet first, the focus indicator second, and browser-specific overrides last.
Sizing leads because every later rule paints inside the field’s final dimensions. The focus indicator precedes decoration because it is the one rule that decides accessibility conformance.
Browser-specific overrides go last since they depend on paint behavior no specification defines. An isolated layer limits the damage when a browser update breaks one, and the trade-off is that early builds look plain until that layer lands.
As verified in October 2026, the autofill workaround exists only because Chrome locks those colors, so delete it the day that lock disappears. Teams ready for the rest of the form can continue with styling the rest of the form.