1. Introduction
This section is non-normative.
User agents have long provided non-standard ways of styling form controls. However, all of these controls are implemented inconsistently across user agents, creating unnecessary friction for authors.
This module aims to define a set of form control parts in enough detail that they can be used interoperably.
It also defines some new ways of customizing form controls, covering common use cases that were previously only possible by implementing custom controls from scratch, which was a lot of work, hard to get right, and often broke either accessibility or platform conventions.
2. Opting Into Basic Appearance: the appearance: base value
Move definition of appearance here.
Name: | appearance |
---|---|
New values: | base |
When applied on a form control, base puts that control in the basic appearance state.
A control that has basic appearance is consistently styleable using standard CSS and the pseudo-elements defined below, and applies overridable default styles that are consistent across UAs. When a control is in that state, the user agent applies styles from the Appendix A: Basic Appearance User Agent Stylesheet to that control.
The user agent must also enable the pseudo-elements defined by § 4 Pseudo-Elements. These pseudo-elements (excluding ::picker()) always inherit appearance from their originating element. The user agent may implement this using an appearance: inherit !important declaration.
NOTE: The inheritance prevents authors from mixing native and non-native parts for the same control.
2.1. Design Principles for the Basic Appearance
The following design principles apply to the design of the basic appearance stylesheet for form controls, in order of descending importance:
-
The styles are identical in every user agent.
-
The controls are recognizable and usable on their own without additional styles.
-
The controls pass 100% of WCAG 2.2 AA standards.
-
The styles are consistent across controls…
-
…in look & feel.
-
…in how they are defined in code.
-
…in sizing and interaction.
-
-
The styles are easily adapted to the website’s branding, without needing complex reset stylesheets:
-
They use minimal code and are easy to override.
-
They do not have a strong voice & tone of their own, and are visually as simple as possible.
-
They inherit page styles rather than define new styles whenever possible.
-
They are resilient to adjustments…
-
…when changed themselves (e.g. changing font, border, layout).
-
…when put in context (e.g. ready to be flex or grid children).
-
-
-
They are comprehensive:
-
Covering all states for each control.
-
Supporting all writing modes and color schemes.
-
For HTML form controls specifically, these principles are applied through the required user agent stylesheet defined in Appendix A: Basic Appearance User Agent Stylesheet.
2.2. Examples
Refine these examples through implementation, experimentation, bikeshedding and improvements to the user agent stylesheet.
The main purpose of these examples is to show how the design principles for the basic appearance apply in practice.
To apply the basic appearance on individual controls, the following code is used:
input, meter, progress, button, select{ appearance : base; }
NOTE: The form layout used by the following examples is not detailed.
2.2.1. Default User Agent Colors
Here are the basic appearance colors inheriting respectively the default light and dark mode colors from the root element:
2.2.2. Color/Font Customization
Here are some examples of customization being done on top of the basic appearance:
form{ font-family : "American Typewriter" ; background-color : rgb ( 254 , 252 , 221 ); color : rgb ( 131 , 17 , 0 ); } input, meter, progress, button, select{ appearance : base; }
form{ font-family : "Courier New" ; font-size : 14 px ; background-color : rgb ( 0 , 0 , 0 ); color : rgb ( 0 , 249 , 0 ); } input, meter, progress, button, select{ appearance : base; }
3. Styling Pickers
3.1. The ::picker() pseudo-element
The ::picker() pseudo-element represents the part of the form control that pops out of the page.
::picker() = ::picker( <form-control-identifier>+ ) <form-control-identifier> = select
The ::picker() pseudo-element only matches when the originating
element supports basic appearance and has a popup picker. The
specified <ident> must also match the unique picker name of the originating element. For example, the unique picker name for
the select
element is select.
In order for the ::picker() pseudo-element to be rendered, it and its originating element must both have a computed appearance of base.
select, select::picker ( select) { appearance : base; } select::picker ( select) { border : 5 px solid red; background-color : blue; }
NOTE: The non-functional form of ::picker() currently doesn’t work to prevent unintended styling of pickers as new pickers become supported. Once styling for all form control pickers is finalized, this non-functional form will work for all pickers.
4. Pseudo-Elements
Form controls are composed of many parts that authors may want to style separately, hence the need for user agents to provide pseudo-elements for individual form controls.
The section below introduces a set of pseudo-elements that attempts to cover the most common use cases, so they can be addressed in a consistent manner across user agents.
Control | Pseudo-elements |
---|---|
<progress>
|
├─ ::track │ └─ ::fill └─ ::thumb |
<meter>
| |
<input type=checkbox switch>
| |
<input type=range>
| |
<input type=checkbox>
| ::checkmark |
<input type=radio>
| |
<input type=file>
| ::file-selector-button |
<input type=date>
| |
<input type=datetime-local>
| |
<input type=time>
| |
<input type=month>
| |
<input type=week>
| |
<input type=text>
| See § 4.5 Styling Parts for Text Fields: the ::field-text and ::clear-icon pseudo-elements |
<input type=search>
| |
<input type=password>
| |
<input type=number>
| See § 4.6 Styling Parts for Number Fields: the ::step-control, ::step-up and ::step-down pseudo-elements |
<input type=color>
| |
<select>
| ::picker-icon |
<option>
| ::checkmark |
Buttons |
4.1. Picker Opener Icon: the ::picker-icon pseudo-element
The ::picker-icon pseudo-element represents the part of the control that represents the icon denoting the presence of the picker. It is only generated when the originating element has basic appearance and if it opens a picker. It is a fully styleable pseudo-element and inherits from its originating element.
::picker-icon generates a box as if it was an child of its originating element, after any boxes generated by the ::after pseudo-element, with content as specified by content.
4.2. File Selector Button: the ::file-selector-button pseudo-element
The ::file-selector-button pseudo-element represents the button used to open a file picker, if the UA renders such a button.
It typically targets the button
inside an input
element with type=file
.
It is an element-backed pseudo-element.
::file-selector-button{ border : 3 px solid green}
4.3. Styling Checkmarks: the ::checkmark pseudo-element
The ::checkmark pseudo-element represents an indicator of whether the item is checked, and is present on checkboxes, radios, and option elements.
It is only generated when the originating element supports the :checked pseudo-class, and either has basic appearance or an ancestor with basic appearance. It is a fully styleable pseudo-element and inherits from its originating element.
For checkboxes and radio elements, it generates a box as if it was an child of its originating element, between the boxes generated by the ::before and ::after pseudo-element, with content as specified by content.
For option elements, it generates a box as if it was an child of its originating element, preceding any boxes generated by the ::before pseudo-element, with content as specified by content.
::checkmark{ background-image : url ( ... ) }
It may also be used in combination with :indeterminate
to style the indeterminate checkmark:
:indeterminate::checkmark{ background-image : url ( ... ) }
4.4. Styling Parts of Slider-Like Controls: the ::thumb, ::track and ::fill pseudo-elements
Naming is still under discussion. [Issue #9830]
Slider-like controls are form controls that represent progress. That progress may be adjustable by the user.
The following pseudo-elements are provided to style their different parts:
- ::thumb
-
The ::thumb pseudo-element represents
the portion that allows the user to adjust the progress of the control.
NOTE: It is typically natively rendered as a circle in most user agents.
- ::track
- The ::track pseudo-element represents the portion containing both the progressed and unprogressed portions of the control.
- ::fill
-
The ::fill pseudo-element represents
the portion containing the progressed portion of the control.
When the progress of control is undetermined (like with <progress indeterminate>), the user agent must give this portion an inline-size of zero.
These pseudo-elements are fully styleable pseudo-elements and their structure is the following:
<input type="range"> ├─ ::track │ └─ ::fill └─ ::thumb
The list of slider-like controls depends on the host language. For HTML, this corresponds to:
4.5. Styling Parts for Text Fields: the ::field-text and ::clear-icon pseudo-elements
- ::field-text
- The ::field-text pseudo-element represents
the portion of the
input
that contains the editable text. - ::clear-icon
-
The ::clear-icon pseudo-element represents
the portion of the
input
that allows the user to clear theinput
when clicked if provided by the user agent.With appearance: textfield, the user agent must not generate this part.
::field-text and ::clear-icon must be siblings.
Collect parts used by autofill.
Define something for the password visibility toggle for user agents implementing it?
4.6. Styling Parts for Number Fields: the ::step-control, ::step-up and ::step-down pseudo-elements
These pseudo-elements are provided for number inputs. They are fully styleable pseudo-elements.
- ::step-control
- The ::step-control pseudo-element represents the portion of a number input that contains the up and down buttons.
- ::step-up
- The ::step-up pseudo-element represents the button that increments the value inside a number input when activated.
- ::step-down
- The ::step-down pseudo-element represents the button that decrements the value inside a number input when activated.
Their structure is defined as follows:
<input type="number"> ├─ ::field-text └─ ::step-control ├─ ::step-up └─ ::step-down
< input type = "number" >
can be re-styled like this:
[ + 2 - ]
using the following styles:
input[ type=number] { appearance : base; &::step-control{ display : contents; } &::step-up{ order : 1 ; content : "+" ; } &::field-text{ order : 2 ; } &::step-down{ order : 3 ; content : "-" ; } }
With appearance: textfield, the user agent must not generate this part.
4.7. Styling Parts for Date/Time Input Fields: the ::field-component and ::field-separator pseudo-elements
- ::field-component
- The ::field-component pseudo-element represents the portions of the control that contain the date/time component values.
- ::field-separator
- The ::field-separator pseudo-element represents the portions of the control that separate the date/time component values if the user agent provides those portions.
Those pseudo-elements are siblings. The exact structure of the control is determined by internationalization and by the host language, but must be consistent across user-agents.
< input type = "date" >
may render like this in US locales:
[ 08 / 22 / 2024 [v]]
The resulting tree is:
<input type="date"> ├─ ::field-component (08) ├─ ::field-separator (/) ├─ ::field-component (22) ├─ ::field-separator (/) ├─ ::field-component (2024) └─ ::picker-icon
4.8. Compatibility With Vendor Pseudo-Element Extensions
When possible, the user agent should use aliasing to implement any non-standard pseudo-elements.
When not possible, the user agent must reserve the standard pseudo-elements for appearance: base and use any non-standard ones for appearance: none.
5. Pseudo-Classes
5.1. Targeting Different Meter States: the :low-value / :high-value / :optimal-value pseudo-classes
Make sure this is able to replicate UA logic. [Issue #11336]
Link these to the HTML definitions.
The :low-value pseudo-class matches on a meter
element when its value is under the value specified by the low
HTML attribute.
The :high-value pseudo-class matches on a meter
element when its value is over the value specified by the high
HTML attribute.
The :optimal-value pseudo-class matches on a meter
element when its value is in the range determined by the optimum
/ low
/ high
HTML attributes.
5.2. Targeting Selects that are Listboxes
Define something. [Issue #7422]
6. The control-value() function
This is not ready for implementation, file an issue regarding this.
Consider privacy implications, regarding data exfiltration.
The control-value() function computes to the current value of the form control it is on. If it is used on an element that is not a form control, it returns an empty string.
<control-value()> = control-value( <type>? ) <type> = '<' [ number | string ] '>'
If used on a pseudo-element, it is evaluated against its originating element.
input[ type=range] ::after{ content : control-value (); }
7. Styling Widgets
Move field-sizing/accent-color/input-security into this specification?
7.1. Changing the Orientation of a Slider-Like Control: slider-orientation
Name: | slider-orientation |
---|---|
Value: | auto | left-to-right | right-to-left | top-to-bottom | bottom-to-top |
Initial: | auto |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
- auto
- The slider-like control orientation is defined by the writing mode and direction.
- left-to-right
- The slider-like control is rendered horizontally and ::fill is left-aligned within the control.
- right-to-left
- The slider-like control is rendered horizontally and ::fill is right-aligned within the control.
- top-to-bottom
- The slider-like control is rendered vertically and ::fill is top-aligned within the control.
- bottom-to-top
- The slider-like control is rendered vertically and ::fill is bottom-aligned within the control.
Appendix A: Basic Appearance User Agent Stylesheet
This section needs refining with implementation.
Basics
input, textarea, button, ::file-selector-button, select, meter, progress{ color : inherit; font : inherit; box-sizing : border-box; background-color : transparent; }
Layout
input : not ([ type=file], [ type=range]), textarea, button, ::file-selector-button, ::track, select, meter, progress{ border : 1 px solid currentColor; background-color : transparent; }
Sliders
Refine meter, progress, switch and range input styling.
::track{ height : 1 em ; } ::fill{ height : 100 % ; background-color : currentColor; } ::thumb{ border-radius : 0 ; border : none; background-color : currentColor; appearance : none; width : 1 em ; height : 100 % ; }
Checkboxes & radios
input : is ([ type=checkbox] :not ([ switch]), [ type=radio]) { width : 1 em ; height : 1 em ; display : inline-flex; align-items : center; justify-content : center; } input[ type=checkbox] :not ([ switch]) ::checkmark{ content : '\2713' /'' ; } input[ type=radio] ::checkmark{ content : '' ; background-color : currentColor; display : inline-block; border-radius : 100 % ; height : 100 % ; width : 100 % ; }
Selects & buttons
button, select, input[ type="color" ] { border : 1 px solid currentColor; background-color : transparent; color : inherit; /* Padding prevents the text from sticking to the borders. * optically centered to account for half leading */ padding-block:0.25 em ; padding-inline : 0.5 em ; /* <select>s and <button>s should have border-radius to be * distinct from <input>s: https://github.com/w3c/csswg-drafts/issues/10857#issuecomment-2520875011*/ border-radius:0.5 em ; /* These min-size rules ensure accessibility by following WCAG rules: * https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html * The 1.2em is there to make sure that options without text don’t change * the block size of the button. */ min-block-size:max ( 24 px , 1 lh ); min-inline-size : 24 px ; /* box-sizing comes from existing UA styles which happen to * already be interoperable. */ box-sizing: border-box; /* Push picker icon to the right of the box and have some space * in between it and the text. */ display: inline-flex; gap : 1 em ; } :is ( button, select, input[ type="color" ]) :enabled:hover{ background-color : color-mix ( in lab, currentColor10 % , transparent); } :is ( button, select, input[ type="color" ]) :enabled:active{ background-color : color-mix ( in lab, currentColor20 % , transparent); } :is ( button, select, input[ type="color" ]) :disabled{ color : color-mix ( in srgb, currentColor50 % , transparent); } select > button:first-child{ /* Prevents button from setting font, color, or background-color */ all: unset; /* Prevents duplicate box decorations */ display: contents; /* Prevents button activation behavior so select can handle events */ interactivity: inert; } select option{ /* These min-size rules ensure accessibility by following WCAG rules: * https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html * Unset if the author provides a child button element. * The 1lh is there to make sure that options without text don’t change * the block size of the option. */ min-inline-size:24 px ; min-block-size : max ( 24 px , 1 lh ); /* Centers text within the block (vertically). From OpenUI discussion: * https://github.com/openui/open-ui/issues/1026#issuecomment-2103187647. */ align-content: center; /* centering + gap between checkmark and option content */ /* also easily reversed, when checkmark should be inline-end */ display: flex; align-items : center; gap : 0.5 em ; /* Makes options with long text widen picker instead * of making options tall. */ white-space: nowrap; } select option:enabled:hover{ background-color : color-mix ( in lab, currentColor10 % , transparent); } select option:enabled:active{ background-color : color-mix ( in lab, currentColor20 % , transparent); } select option:disabled{ color : color-mix ( in lab, currentColor50 % , transparent); } select option::checkmark{ content : '\2713' /'' ; } select option:not ( :checked) ::checkmark{ visibility : hidden; } select optgroup{ /* font-weight makes optgroups visually distinct from options. */ font-weight: bolder; } select optgroup option{ /* Undo font-weight:bolder rule from optgroups. */ font-weight: normal; } select legend, select option{ /* spacing ownership moves to children */ /* space inline from border edges */ /* this creates a full bleed hover highlight */ padding-inline:0.5 em ; } select::picker-icon{ /* margin-inline-start pushes the icon to the right of the box */ margin-inline-start: auto; display : block; content : counter ( -ua-disclosure-open, disclosure-open); } ::picker ( select) { /* Same properties as popover and dialog */ color: CanvasText; background-color : Canvas; border : 1 px solid; /* box-sizing is set to match the button. */ box-sizing: border-box; /* Remove [popover] padding which * prevents options from extending to edges */ padding:0 ; /* Anchor positioning and scrollbars */ inset: auto; margin : 0 ; min-inline-size : anchor-size ( self-inline); min-block-size : 1 lh ; /* Go to the edge of the viewport, and add scrollbars if needed. */ max-block-size: stretch; overflow : auto; /* Below and span-right, by default. */ position-area: block-end span-inline-end; position-try-order : most-block-size; position-try-fallbacks : /* First try above and span-right. */ block-start span-inline-end, /* Then below but span-left. */ block-end span-inline-start, /* Then above and span-left. */ block-start span-inline-start; }
Appendix B: Explorations
Basic Styling Proposals
This section sketches a few proposals for solving the form styling problem.
Prototypes
This idea, originally suggested by fantasai, is that we can style a handful of "prototype" elements. Browser UI designers can then take the styling of those elements and extrapolate the design into their own UIs. At minimum, things like text, backgrounds, and borders can be used. At the limit, things like internal padding, border-radius, etc might be used.
@control button{ <declaration-list>} @control input{ <declaration-list>} input::selection{ <declaration-list>} ...
You would be able to use styles for:
font selection (css-fonts)
text decoration (css-text-decor)
text layout (css-text)
backgrounds & drop-shadow (css-backgrounds)
borders & padding (css-backgrounds)
filters
anything else we or a UA decides seems relevant
Most form controls, even a calendar widget or clock, are a combination of these three primitives in some way. If the UA is given the styling for these three primitives, and perhaps one or two more it can figure out how to style the rest.
For example, a calendar widget might have the month, the year, some buttons to move them around, the ability to click into them and edit them directly, and a representation of the days of the month. The selected day is selected. Perhaps the buttons only show up on :hover or :focus -- the UA decides. But it knows that a button should be this particular shade of blue with that particular border-radius and drop-shadow. The calendar might be shown in the colors of the input field, and the selected day in the selection color, and in all ways it will match the way the input fields look in the rest of the page.
Now, the author can’t decide, for example, the spacing between the year and the month name, or whether the button to change years has a solid arrow or a hollow arrow or a frilly one, and she can’t decide that there should be a black solid half-border between the month and the day field below it with 5px spacing. But the calendar will look like it belongs to the page, and the UA can come up with a different calendar layout when it ships one on a wide but short smart watch where the month and year are better placed on the side without breaking anything.

The black area is the button color; a very light transparency of it can be the glass color. The rollers are the input colors.

It’s hard to tell without more context, but for the one on the right, the clock face and the digital readout are @input colors, the highlighted bits are the highlight color, the Done button is the button styling, and the shaded area around the clock face is the same color as the button background.
Inverse System Colors
This idea, originally sketched by Florian and Tab, is to define an abstract set of colors that UI designers can then choose from when coloring their UI.
Tab’s suggested set of colors, from an Android color-extraction API proposal:
-
Light/Normal/Dark Vibrant
-
Light/Normal/Dark Muted
-
Vibrant Complementary (for call-out buttons and such that need to be visually distinct)
(where light ~75% lightness, normal is ~50%, dark is ~25%; vibrant is at least 30% saturation, ideally 100%, and muted is at most 40% saturation, ideally 30%)
-
Light/Dark Contrasting Text
-
Light/Dark Contrasting Secondary Text
That’s 11 colors, many of which should be drawable from the webpage’s own color scheme. We can auto-gen a bunch of them if you specify at least some of them, like genning the light/dark variants from the "normal-vibrant" color, or automatically setting text colors to white/black as appropriate.
There’s no guarantee that the input UIs will use the colors in the same way that the rest of the page does, though.
Miscellaneous Control-Specific Suggestions
<progress> and <meter> styling
Insert <select> and <datalist> styling proposal and/or whiteboard photo.
Select/Datalist Dropdown
-
Only allow styling if both color and background are set.
-
Option container:
-
backgrounds
-
borders
-
padding
-
-
-
padding
-
borders
-
border-collapse?
-
backgrounds
-
display-inside is allowed, automatically blockified
-
not margins, position, float, width, height
-
All options are contain:paint and BFCs.
Input UI Examples
This section catalogues as many input UI examples as we can screenshot, especially on mobile devices where they’re a bit "weirder".
Time Pickers



Date Pickers

Acknowledgements
Thanks to Aditya Keerthi, Anne van Kesteren, Elika Etemad, Jen Simmons, Joey Arhar, Jon Davis, Simon Fraser and Theresa O’Connor for their input on this proposal.
Thanks to Ana Tudor for her detailed analysis of input[type=range]
styling.