Title: CSS Form Control Styling Level 1
Shortname: css-forms
Level: 1
Repository: w3c/csswg-drafts
Prepare for TR: no
ED: https://drafts.csswg.org/css-forms-1/
TR: https://www.w3.org/TR/css-forms-1/
Editor: Tim Nguyen, w3cid 137443, Apple Inc. https://apple.com/, ntim@apple.com
Abstract: This CSS Module defines various ways of styling form controls and their different parts.
Group: csswg
Status: ED
Work Status: Exploring
Markup Shorthands: css yes, markdown yes
# Introduction # {#intro}
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.
# Opting Into [=Basic Appearance=]: the ''appearance: base'' value # {#appearance}
ISSUE: 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 [[#basic-appearance-stylesheet]] to that control.
The user agent must also enable the pseudo-elements defined by [[#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.
## Design Principles for the [=Basic Appearance=] ## {#basic-appearance-principles}
The following design principles apply to the design of the [=basic appearance=] stylesheet for form controls,
in approximate order of descending importance:
1. The styles are identical in every user agent.
1. The controls are recognizable and usable on their own without additional styles.
1. The controls pass 100% of WCAG 2.2 AA standards.
1. The styles are consistent across controls…
1. …in look & feel.
1. …in how they are defined in code.
1. …in sizing and interaction.
1. The styles are easily adapted to the website’s branding, without needing complex reset stylesheets:
1. They use minimal code and are easy to override.
1. They do not have a strong voice & tone of their own, and are visually as simple as possible.
1. They inherit page styles rather than define new styles whenever possible.
1. They are resilient to adjustments…
1. …when changed themselves (e.g. changing font, border, layout).
1. …when put in context (e.g. ready to be flex or grid children).
1. They are comprehensive:
1. Covering all states for each control.
1. Supporting all writing modes and color schemes.
For HTML form controls specifically, these principles are applied through the required user agent stylesheet defined in [[#basic-appearance-stylesheet]].
## Examples ## {#ex-appearance}
ISSUE: 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:
NOTE: The form layout used by the following examples is not detailed.
### Default User Agent Colors ### {#colors}
Here are the [=basic appearance=] colors inheriting respectively the default light and dark mode colors from the root element:
### Color/Font Customization ### {#custom}
Here are some examples of customization being done on top of the [=basic appearance=]:
# Styling Pickers # {#pickers}
## The ''::picker()'' pseudo-element ## {#picker-pseudo}
The ::picker() pseudo-element represents the part of the form control that pops out of the page.
::picker() = ::picker( <>+ )
<> = select
The ''::picker()'' pseudo-element only matches when the [=originating
element=] supports [=basic appearance=] and has a popup picker. The
specified <> 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 value|computed=] 'appearance' of ''appearance/base''.
The following styles apply the [=basic appearance=] to the select picker and the select and add some additional styling to the picker:
```css
select, select::picker(select) {
appearance: base;
}
select::picker(select) {
border: 5px 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.
# Pseudo-Elements # {#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.
Informative overview of form control pseudo-elements as applied to HTML
* ''::field-component''
* ''::field-separator''
* ''::picker-icon''
See [[#date-time-pseudos]]
``
``
``
``
`` (with no type)
See [[#field-pseudos]]
``
``
``
``
``
``
``
See [[#number-pseudos]]
``
''::color-swatch''
`
See [[#textarea-pseudos]]
`
''::picker-icon''
`
''::checkmark''
Buttons
ISSUE: Add illustrations.
## Picker Opener Icon: the ''::picker-icon'' pseudo-element ## {#picker-icon}
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'.
## File Selector Button: the ''::file-selector-button'' pseudo-element ## {#file-selector-button-pseudo}
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=].
For example, the following example should show a green border around the
file selector button:
::file-selector-button { border: 3px solid green }
## Styling Checkmarks: the ''::checkmark'' pseudo-element ## {#checkmark}
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'.
The following example changes the background image of a checkmark:
::checkmark { background-image: url(...) }
It may also be used in combination with `:indeterminate` to style the indeterminate checkmark:
## Styling Parts of [=Slider-Like Controls=]: the ''::slider-thumb'', ''::slider-track'' and ''::slider-fill'' pseudo-elements ## {#slider-pseudos}
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:
::slider-thumb
The ''::slider-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.
::slider-track
The ''::slider-track'' pseudo-element represents the portion containing
both the progressed and unprogressed portions of the control.
::slider-fill
The ''::slider-fill'' pseudo-element represents
the portion containing the progressed portion of the control.
When the progress of control is indeterminate (like with ''<progress>''),
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:
```
├─ ::slider-track
│ └─ ::slider-fill
└─ ::slider-thumb
```
The list of [=slider-like controls=] depends on the host language. For HTML, this corresponds to:
- <{progress}>
- <{meter}>
- <{input}> elements with `type="range"`
- <{input}> elements with `type="checkbox" switch`
## Styling Parts for Text Fields: the ''::field-text'' and ''::clear-icon'' pseudo-elements ## {#field-pseudos}
::placeholder
The ''::placeholder'' pseudo-element represents
the portion of the <{input}> that contains the placeholder text.
::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 the <{input}> 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.
ISSUE: Collect parts used by autofill.
ISSUE(11845): Define something for the password visibility toggle for user agents implementing it?
ISSUE(11844): Define how `::placeholder` interacts with `::field-text`.
## Styling Parts for textareas: the ''::placeholder'' and ''::field-text'' pseudo-elements ## {#textarea-pseudos}
''::placeholder''
The ''::placeholder'' pseudo-element represents
the portion of the <{textarea}> that contains the placeholder text.
''::field-text''
The ''::field-text'' pseudo-element represents
the portion of the <{textarea}> that contains the editable text.
ISSUE(11850): Define something for the resizer.
ISSUE(11844): Define how `::placeholder` interacts with `::field-text`.
## Styling Parts for Number Fields: the ''::step-control'', ''::step-up'' and ''::step-down'' pseudo-elements ## {#number-pseudos}
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:
```
├─ ::field-text
└─ ::step-control
├─ ::step-up
└─ ::step-down
```
The following control:
can be re-styled like this:
`[ + 2 - ]`
ISSUE: Insert real image.
using the following styles:
```css
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.
## Styling Parts for Date/Time Input Fields: the ''::field-component'' and ''::field-separator'' pseudo-elements ## {#date-time-pseudos}
::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.
The following control:
may render like this in US locales:
[ 08 / 22 / 2024 [v]]
ISSUE: Insert real image.
The resulting tree is:
```
├─ ::field-component (08)
├─ ::field-separator (/)
├─ ::field-component (22)
├─ ::field-separator (/)
├─ ::field-component (2024)
└─ ::picker-icon
```
## Color Swatch: the ''::color-swatch'' pseudo-element ## {#color-swatch-pseudo}
The ::color-swatch pseudo-element represents the portion of the control that displays the chosen color value.
For example, the following example should make the input and its color swatch rounded:
## Compatibility With Vendor Pseudo-Element Extensions ## {#vendor-pseudo-element-compatibility}
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''.
# Pseudo-Classes # {#pseudo-classes}
## Targeting Different Meter States: the '':low-value'' / '':high-value'' / '':optimal-value'' pseudo-classes ## {#meter-values}
ISSUE(11336): Make sure this is able to replicate UA logic.
ISSUE: 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.
## Targeting Selects that are Listboxes ## {#selects-listboxes}
ISSUE(7422): Define something.
# The ''control-value()'' function # {#control-value}
ISSUE: This is not ready for implementation, file an issue regarding this.
ISSUE(11860): Consider privacy implications, regarding data exfiltration.
ISSUE(11842): Consider adding more types.
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.
# Styling Widgets # {#styling-widgets}
## Widget Accent Colors: the 'accent-color' property ## {#accent-color}
The 'accent-color' property is defined in [[!CSS-UI-4]].
## Switching form control sizing: the 'field-sizing' property ## {#field-sizing}
Name: field-sizing
Value: fixed | content
Initial: ''field-sizing/fixed''
Applies to: [=elements with default preferred size=]
Inherited: no
Percentages: N/A
Computed Value: as specified
Canonical order: per grammar
Animation type: discrete
For the purpose of this specification,
an element with default preferred size is an element
whose [=intrinsic size=] is fixed regardless of the size of its content.
The host language defines which elements are applicable to it.
For example, in HTML <{textarea}> is an [=element with default preferred size=].
fixed
For [=element with default preferred size=],
the UA must set the [=intrinsic size=]
to the default preferred size defined by the host language for that element.
Otherwise, the UA must behave the same as ''field-sizing/content''.
content
The UA must determine the element's [=intrinsic size=] based on its content,
and must ignore any default preferred size defined by the host language for that element.
If the element is an [=element with default preferred size=] and
is listed in [[CSS-SIZING-3#min-content-zero|compressible replaced elements]],
the UA must stop treating the element as a replaced element for [=min-content contribution=].
For instance, <{textarea}> has a fixed size regardless of its content by default:
⎸The quick brown fox jumps over the lazy dog.
If ''field-sizing: content'' is applied, the size of the former should fit to a text caret.
⎸
If ''field-sizing: content'' is applied and its width property has a fixed value like ''width: 10em'',
the element height depends on the number of the content lines:
The quick brown fox jumps over the lazy dog.⎸
## Changing the Orientation of a [=Slider-Like Control=]: ''slider-orientation'' ## {#slider-orientation}
ISSUE: Rework this property.
Name: slider-orientation
Value: auto | left-to-right | right-to-left | top-to-bottom | bottom-to-top
Initial: auto
Inherited: no
Percentages: n/a
Computed Value: as specified
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 ''::slider-fill'' is left-aligned within the control.
right-to-left
The [=slider-like control=] is rendered horizontally and ''::slider-fill'' is right-aligned within the control.
top-to-bottom
The [=slider-like control=] is rendered vertically and ''::slider-fill'' is top-aligned within the control.
bottom-to-top
The [=slider-like control=] is rendered vertically and ''::slider-fill'' is bottom-aligned within the control.
## Obscuring sensitive input: the 'input-security' property ## {#input-security}
Issue: The CSSWG has agreed that
while we believe that providing this piece of functionality to users is important,
doing it via CSS+JS is the wrong approach,
and that instead it should be built into user agents:
this needs to work consistently from site to site for it to be discoverable and understandable by users,
this needs to work even when JS is turned off,
and this needs to have consistently solid accessibility…
We therefore intend to remove this from the specification,
and instead, we would like to see this behavior specified in the HTML specification
as part of the interaction model of password fields.
Holding off deleting until the situation with HTML is clarified.
See
https://github.com/w3c/csswg-drafts/issues/6788
and
https://github.com/whatwg/html/issues/7293.
Name: input-security
Value: auto | none
Initial: ''input-security/auto''
Applies to: [=sensitive text inputs=]
Inherited: no
Percentages: N/A
Computed Value: as specified
Canonical order: per grammar
Animation type: by computed value type
For the purpose of this specification,
a sensitive text input is
a text input whose purpose is to accept sensitive input,
as defined by the host language.
For example, in HTML <{input/type/password|<input type=password>}> is a [=sensitive text input=].
By default, user agents obscure the contents of [=sensitive text inputs=]
in order to prevent onlookers from seeing it.
Users may wish to temporarily disable this obscuring
in order to confirm that they've typed their sensitive information correctly.
The ''input-security'' property may be used by authors
to enable or disable this obscuring.
none
The UA must not obscure the text in the control,
so that it can be read by the user.
auto
The UA should obscure the text in the control,
so that it cannot be read by the user.
While the exact mechanism by which user agents obscure the text in the control is undefined, user agents typically obscure [=sensitive text inputs=] by replacing each character with some suitable replacement such as U+002A ASTERISK (*) or U+25CF BLACK CIRCLE (●).
For instance, given this style sheet
input[type=password] {
input-security: auto;
}
and this HTML
<input type=password value=MySecret794>
a user agent might render the <{input/type/password|<input type=password>}> like so:
●●●●●●●●●●●
Appendix A: Basic Appearance User Agent Stylesheet