Text fields cut into the surface, with labels, hints, errors and groups.
On this page
A text field is a well cut into its surface, edged by a half-pixel contour. On focus it rises to the sheet and takes a single ink contour with a soft halo. Use .input on <input> and <textarea>, and .field on the <label> that holds it.
Connect a hint or an error to its field with aria-describedby, and mark an invalid value with aria-invalid="true". A field-error shows only while its field is invalid, and replaces the hint while it shows, so both can be in the markup from the start.
Hint and error
<labelclass="field">Project name<inputclass="input"value="atlas"aria-describedby="name-hint"><spanclass="field-hint"id="name-hint">Lowercase letters, numbers and dashes.</span></label><labelclass="field">Domain<inputclass="input"value="atlas"aria-invalid="true"aria-describedby="domain-error"><spanclass="field-error"id="domain-error">Enter a full domain, such as atlas.dev.</span></label>
The same states follow the browser's own constraints, without script. A field with required, a type or a pattern takes the error contour through :user-invalid, which matches only after the reader has edited the field or tried to submit, never on a fresh form. The field-error appears with it.
Native validation
<formclass="flexflex-colgap-md"><labelclass="field">Work email<inputclass="input"type="email"requiredplaceholder="you@company.com"aria-describedby="work-email-hint work-email-error"><spanclass="field-hint"id="work-email-hint">Type something that is not an email, then leave the field.</span><spanclass="field-error"id="work-email-error">Enter an email address, such as ada@example.com.</span></label><buttontype="submit"class="btn btn-primary self-start">Continue</button></form>
A field is 32, 38 or 44px tall: the heights of a button, a select and a segmented control, so any of them share a row with no step between them. On touch screens every size is 44px.
A <textarea> with .input keeps the rows you give it and a vertical resize handle. Add input-auto to let it grow with its content instead: field-sizing: content sizes it from three lines up to twelve, then it scrolls. Set --input-rows and --input-max-rows to change the range. In a browser without field-sizing it keeps its minimum height and the handle.
Textarea
<labelclass="field">Message<textareaclass="input"rows="3"placeholder="A fixed height with a resize handle"></textarea></label><labelclass="field">Notes<textareaclass="input input-auto"placeholder="Keep typing: this one grows with its content"></textarea></label>
input-group is one well around a bare field and what belongs to it: an icon, a unit, a shortcut or a button. The group draws the hover, focus, invalid and disabled states of the field inside it through :has(); the field itself, an input-group-field, keeps the semantics and the caret. Make the group a <label> so the whole well focuses the field.
A button inside the group sits 4px from the well's edge and takes a corner 4px smaller than the well's, so the two stay concentric at every size. Use a <div> for the group when it holds a button.
input-group-wrap turns the group into a field that collects several values: recipients, tags, filters. Each value is a chip in the well, the field takes what is left of the last line, and the well grows by a line when the values need one. A chip sits 6px in from the well's edge and takes the concentric corner.
The stylesheet lays the values out; adding one on Enter and removing the last on Backspace is a few lines of your own script, or a round trip to the server.
Several values
<divclass="field max-w(420)mx-auto"><labelfor="input-teams">Teams</label><divclass="input-group input-group-wrap"><spanclass="chip">Design<buttontype="button"class="chip-remove"aria-label="Remove Design"></button></span><spanclass="chip">Research<buttontype="button"class="chip-remove"aria-label="Remove Research"></button></span><spanclass="chip">Engineering<buttontype="button"class="chip-remove"aria-label="Remove Engineering"></button></span><spanclass="chip">Operations<buttontype="button"class="chip-remove"aria-label="Remove Operations"></button></span><inputclass="input-group-field"id="input-teams"placeholder="Add a team"></div></div>
A type="search" field keeps the browser's clear button, redrawn as a fine cross in the ink of an icon. When the browser fills a field in, the well takes a faint tint of the info color in place of the engine's own yellow or blue, so a filled form still looks like this one.
.input draws the browser's own date, time, number and color fields in the same well. The picker stays the platform's, which is the one a reader already knows on their device. A color field becomes a square whose swatch sits 4px inside, with a concentric corner.
The small marks a browser puts in these fields are redrawn at the weight of an icon and in its ink: a calendar or a clock that opens the picker, and a pair of chevrons that step a number, the upper one up and the lower one down. Chromium and Safari expose those parts; Firefox keeps its own. For a number a reader steps often, use a stepper.
Date, time, number and color
<labelclass="field">Starts<inputclass="input"type="date"value="2026-11-11"></label><labelclass="field">At<inputclass="input"type="time"value="09:30"></label><labelclass="field">Timeout in seconds<inputclass="input"type="number"min="0"step="5"value="30"></label><labelclass="field">Accent<inputclass="input"type="color"value="#5b4fe0"></label>
fieldset on a <fieldset> groups related controls under one caption without the browser's groove, and fieldset-legend gives the <legend> the voice of a field label. Use it around checkboxes, radios and choices, so the group has a name.
Fieldset
<fieldsetclass="fieldset"><legendclass="fieldset-legend">Notify me about</legend><labelclass="check"><inputtype="checkbox"class="checkbox"checked>Failed deployments</label><labelclass="check"><inputtype="checkbox"class="checkbox"checked>Expiring certificates</label><labelclass="check"><inputtype="checkbox"class="checkbox">Product news</label></fieldset>
The field component joins a label, a control, a hint and an error by their ids. The control stays the page's own markup, and everything else on this page is markup too. Copy it into the project with npx master-ui add field; React, Svelte and Vue says how the components are built and used.
ui/field.tsx
import{ useId,type ComponentProps,type ReactNode }from'react'exportinterfaceFieldPropsextendsOmit<ComponentProps<'div'>,'children'>{label:ReactNode/** Guidance under the control. */hint?:ReactNode/** What is wrong with the value. The stylesheet shows it while the control is invalid. */error?:ReactNode/** The control: spread what it is given on an input, a select, a textarea or a component that takes them. */children:(control:{id:string,'aria-describedby'?:string,'aria-invalid'?:true})=>ReactNode}/** A label, its control, a hint and an error, joined by their ids. */exportfunctionField({ label, hint, error, className, children,...root }:FieldProps){const id =useId()const described =[hint !==undefined&&`${id}hint`, error !==undefined&&`${id}error`].filter(Boolean).join(' ')return(<divclassName={className ?`field ${className}`:'field'}{...root}><labelhtmlFor={id}>{label}</label>{children({ id,'aria-describedby': described ||undefined,'aria-invalid': error !==undefined&& error !==false&& error !==null?true:undefined})}{hint !==undefined&&<spanclassName="field-hint"id={`${id}hint`}>{hint}</span>}{error !==undefined&&<spanclassName="field-error"id={`${id}error`}>{error}</span>}</div>)}
ui/Field.svelte
<scriptlang="ts"module>importtype{ Snippet }from'svelte'importtype{ HTMLAttributes }from'svelte/elements'exportinterfaceFieldControl{id:string'aria-describedby'?:string'aria-invalid'?:true}exportinterfaceFieldPropsextendsOmit<HTMLAttributes<HTMLDivElement>,'children'>{label:string/** Guidance under the control. */hint?:string/** What is wrong with the value. The stylesheet shows it while the control is invalid. */error?:string|null/** The control: spread what it is given on an input, a select, a textarea or a component that takes them. */children:Snippet<[FieldControl]>}</script><scriptlang="ts">let{ label, hint, error,class: className, children,...root }:FieldProps=$props()const id =$props.id()const described =$derived([hint !==undefined&&`${id}hint`, error !==undefined&&`${id}error`].filter(Boolean).join(' '))</script><!--@component A label, its control, a hint and an error, joined by their ids.--><divclass={['field', className]}{...root}><labelfor={id}>{label}</label>{@renderchildren({ id,'aria-describedby': described ||undefined,'aria-invalid': error !==undefined&& error !==null?true:undefined})}{#if hint !==undefined}<spanclass="field-hint"id="{id}hint">{hint}</span>{/if}{#if error !==undefined}<spanclass="field-error"id="{id}error">{error}</span>{/if}</div>
ui/Field.vue
<scriptsetuplang="ts">import{ computed, useId }from'vue'defineOptions({inheritAttrs:false})const{ hint, error }=defineProps<{label:string/** Guidance under the control. */hint?:string/** What is wrong with the value. The stylesheet shows it while the control is invalid. */error?:string|null}>()defineSlots<{/** The control: bind what it is given on an input, a select, a textarea or a component that takes them. */default:(control:{'id':string,'aria-describedby'?:string,'aria-invalid'?:true})=>unknown}>()/* A label, its control, a hint and an error, joined by their ids. */const id =useId()const control =computed(()=>({'id': id,'aria-describedby':[hint !==undefined&&`${id}-hint`, error !==undefined&&`${id}-error`].filter(Boolean).join(' ')||undefined,'aria-invalid': error !==undefined&& error !==null?trueasconst:undefined,}))</script><template><divclass="field"v-bind="$attrs"><label:for="id">{{ label }}</label><slotv-bind="control" /><spanv-if="hint !==undefined"class="field-hint":id="`${id}-hint`">{{ hint }}</span><spanv-if="error !==undefined"class="field-error":id="`${id}-error`">{{ error }}</span></div></template>
A text input, a file input or a textarea, drawn as a well; it also draws the browser's date, time, number, search and color fields
input-sm
32px tall; the default is 38px, a button's height. With input
input-lg
44px tall; the default is 38px, a button's height. With input
input-auto
A textarea that grows with its content, from --input-rows to --input-max-rows lines, then scrolls. With input
input-group
One well around a bare field and its icon, addon or button; it draws the hover, focus, invalid and disabled states of the field inside it
input-group-sm
A group 32px tall; the default is 38px. With input-group
input-group-lg
A group 44px tall; the default is 38px. With input-group
input-group-wrap
A group that collects several values as chips and grows by lines; adding and removing a value is the page's own script or a round trip to the server. With input-group
input-group-field
The bare native field inside a group; it keeps the semantics and the caret
input-group-addon
Fixed text beside the value, such as a protocol, a unit or a domain
field
A label stacked over its control, with optional guidance below; a label element, or a div that holds a label with for
field-hint
Muted guidance under the control; inside a field it steps aside while the error shows
field-error
The danger message; it shows only while a control in the field is invalid
fieldset
A fieldset that groups related controls under one caption, without the browser's groove
fieldset-legend
The legend: the group's caption, in the voice of a field label
/* * Input and field: a text field is a well cut into its surface, edged by a half-pixel contour. * On focus the well rises to the sheet and takes one ink contour with a soft halo. * A field is as tall as the button beside it: 32, 38 and 44px. * A textarea can grow with its content, and an input group holds an icon, an addon or a button in the same well. */@layer components {.input{--input-height:2.375rem;--input-padding-x:.75rem;/* A line of 14px text is 20.3px; 8px over and under keeps it inside the 38px the field shares with a button. */--input-padding-y:.5rem;--input-font-size:.875rem;--input-radius:var(--radius-control);width:100%;min-width:0;min-height:var(--input-height);margin:0;padding:var(--input-padding-y)var(--input-padding-x);border:0;border-radius:calc(var(--input-radius)*var(--corner-scale));corner-shape:var(--corner-shape);background:var(--color-surface-inset);color:var(--color-text-strong);scrollbar-width:thin;scrollbar-color:var(--color-line-strong)transparent;/* One contour and a soft inner shade: the well's tone does the rest of the work of being found. */box-shadow:var(--shadow-pressed);font:400var(--input-font-size)/1.45var(--font-family-sans);font-feature-settings:inherit;caret-color:var(--color-text-strong);resize:vertical;appearance:none;transition-property: box-shadow, background-color;transition-duration:var(--duration-quick);transition-timing-function:var(--easing-glide);&::placeholder{color:var(--color-text-muted);opacity:1;}@media(hover:hover){&:hover:not(:focus,:disabled,[readonly]){background:var(--color-surface-inset-hover);}}&:focus{outline:none;background:var(--color-surface-raised);box-shadow:inset000.5pxvar(--color-line-mark),000.5pxvar(--color-line-mark),0003.5pxvar(--color-focus-halo);}/* * An error keeps its boundary while focused; the focus halo takes the danger tone. * :user-invalid reports native constraints (required, type, pattern) once the reader has tried, without script. */&:is([aria-invalid="true"],:user-invalid){box-shadow:var(--shadow-pressed),inset000.5pxvar(--color-danger),000.5pxcolor-mix(in oklab,var(--color-danger)45%,transparent);&:focus{box-shadow:inset000.5pxvar(--color-danger),000.5pxvar(--color-danger),0003.5pxcolor-mix(in oklab,var(--color-danger)15%,transparent);}}/* * Filled in by the browser: the engine paints its own background and does not let a page replace it, so the * well is laid over it as an inset shadow, tinted just enough to say the value was not typed. */&:autofill{box-shadow:var(--shadow-pressed),inset000100vmaxcolor-mix(in oklab,var(--color-info)7%,var(--color-surface-inset));&:focus{box-shadow:inset000.5pxvar(--color-line-mark),000.5pxvar(--color-line-mark),0003.5pxvar(--color-focus-halo),inset000100vmaxcolor-mix(in oklab,var(--color-info)7%,var(--color-surface-raised));}}&:disabled{color:var(--color-text-disabled);cursor:not-allowed;&::placeholder{color:var(--color-text-disabled);}}/* A search field's clear button, redrawn as a fine cross in the ink of an icon. */&::-webkit-search-cancel-button{width:14px;height:14px;margin-inline:.5rem-.125rem;background:var(--color-text-muted);mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Cpath d='m4.5 4.5 7 7m0-7-7 7'/%3E%3C/svg%3E")center/containno-repeat;appearance:none;cursor:pointer;}/* * The marks a browser puts in a date, a time or a number field, redrawn at the weight of an icon and in its * ink: a calendar or a clock that opens the picker, and a pair of chevrons that step the number. Chromium and * Safari expose these parts; Firefox keeps its own. */&::-webkit-calendar-picker-indicator{box-sizing:border-box;width:1rem;height:1rem;margin-inline:.5rem-.125rem;padding:0;background:var(--color-text-muted);mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 4h9a1 1 0 0 1 1 1v7.5a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM2.5 7h11M5.5 2.5V5M10.5 2.5V5'/%3E%3C/svg%3E")center/containno-repeat;opacity:1;cursor:pointer;}/* The engine pads each part of a date by a pixel, which would make the field taller than its neighbors. */&::-webkit-datetime-edit{margin-block:-1px;}&[type="time"]::-webkit-calendar-picker-indicator{mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2.5a5.5 5.5 0 1 0 0 11a5.5 5.5 0 0 0 0-11M8 5v3l2 1.5'/%3E%3C/svg%3E");}/* The upper half of the mark steps up and the lower half steps down, as the browser's own does. */&::-webkit-inner-spin-button{width:1rem;margin-inline:.5rem-.125rem;background:var(--color-text-muted);mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5.5 6.25 2.5-2.5 2.5 2.5M10.5 9.75 8 12.25 5.5 9.75'/%3E%3C/svg%3E")center/1remno-repeat;opacity:1;appearance:none;cursor:pointer;}&:disabled::-webkit-calendar-picker-indicator{background:var(--color-text-disabled);cursor:not-allowed;}&:disabled::-webkit-inner-spin-button{background:var(--color-text-disabled);cursor:not-allowed;}@media(forced-colors:active){&::-webkit-calendar-picker-indicator{background: FieldText;forced-color-adjust:none;}&::-webkit-inner-spin-button{background: FieldText;forced-color-adjust:none;}}&.input-sm{--input-height:2rem;--input-padding-x:.625rem;--input-padding-y:.3125rem;--input-font-size:.8125rem;--input-radius:var(--radius-control-sm);}&.input-lg{--input-height:2.75rem;--input-padding-x:.875rem;--input-padding-y:.625rem;--input-font-size:1rem;--input-radius:var(--radius-control-lg);}/* * A textarea that grows with its content, from --input-rows to --input-max-rows lines, then scrolls. * Without field-sizing it keeps its minimum height and the resize handle. */&.input-auto{--input-rows:3;--input-max-rows:12;min-height:max(var(--input-height),calc(var(--input-rows)*1lh +var(--input-padding-y)*2));@supports(field-sizing:content){max-height:calc(var(--input-max-rows)*1lh +var(--input-padding-y)*2);field-sizing:content;resize:none;}}/* A file input holds its button like a segmented track holds an option: 4px in, with a concentric corner. */&[type="file"]{padding-block:4px;padding-inline:4pxvar(--input-padding-x);color:var(--color-text-muted);line-height:calc(var(--input-height)-8px);cursor:pointer;&::file-selector-button{height:calc(var(--input-height)-8px);margin-block:0;margin-inline:0.75rem;padding:0.625rem;border:0;border-radius:calc(var(--input-radius)*var(--corner-scale)-4px);corner-shape:var(--corner-shape);@apply--surface-sheet;color:var(--color-text-strong);box-shadow:var(--shadow-raised);font:var(--font-weight-control).78125rem/1var(--font-family-sans);/* On the baseline the button would push its line, and the field, a fraction taller than a button's 38px. */vertical-align:top;cursor:pointer;}&:disabled::file-selector-button{background:transparent;color:var(--color-text-disabled);box-shadow:var(--shadow-flat);cursor:not-allowed;}}/* A color: the swatch fills the well, 4px in, with a concentric corner. */&[type="color"]{width:var(--input-height);padding:4px;cursor:pointer;&::-webkit-color-swatch-wrapper{padding:0;}&::-webkit-color-swatch{border:0;border-radius:calc(var(--input-radius)*var(--corner-scale)-4px);corner-shape:var(--corner-shape);box-shadow:inset000.5pxvar(--color-line-on-color);}&::-moz-color-swatch{border:0;border-radius:calc(var(--input-radius)-4px);}}@media(pointer:coarse){min-height:max(var(--input-height),2.75rem);/* Below 16px iOS zooms the page on focus. */font-size:max(var(--input-font-size),1rem);/* Every size is 44px on touch, and what is derived from the height (a file's button, a swatch) follows. */&:not(.input-lg){--input-height:2.75rem;}}@media(forced-colors:active){outline:1pxsolid FieldText;outline-offset:-1px;&:focus{outline:2pxsolid Highlight;outline-offset:-2px;}}}/* * Input group: one well around a bare field and what belongs to it: an icon, a unit, a shortcut or a button. * The group draws the field's states; the field inside keeps the semantics and the caret. */.input-group{--input-height:2.375rem;--input-padding-x:.75rem;--input-font-size:.875rem;--input-radius:var(--radius-control);--icon-size:1rem;--icon-offset:-.125rem;display:flex;align-items:center;gap:.5rem;min-width:0;min-height:var(--input-height);padding:0var(--input-padding-x);border-radius:calc(var(--input-radius)*var(--corner-scale));corner-shape:var(--corner-shape);background:var(--color-surface-inset);color:var(--color-text-muted);box-shadow:var(--shadow-pressed);font:400var(--input-font-size)/1.45var(--font-family-sans);cursor:text;transition-property: box-shadow, background-color;transition-duration:var(--duration-quick);transition-timing-function:var(--easing-glide);@media(hover:hover){&:hover:not(:has(.input-group-field:is(:focus,:disabled,[readonly]))){background:var(--color-surface-inset-hover);}}&:has(.input-group-field:focus){background:var(--color-surface-raised);box-shadow:inset000.5pxvar(--color-line-mark),000.5pxvar(--color-line-mark),0003.5pxvar(--color-focus-halo);}&:has(.input-group-field:is([aria-invalid="true"],:user-invalid)){box-shadow:var(--shadow-pressed),inset000.5pxvar(--color-danger),000.5pxcolor-mix(in oklab,var(--color-danger)45%,transparent);&:has(.input-group-field:focus){box-shadow:inset000.5pxvar(--color-danger),000.5pxvar(--color-danger),0003.5pxcolor-mix(in oklab,var(--color-danger)15%,transparent);}}&:has(.input-group-field:disabled){color:var(--color-text-disabled);cursor:not-allowed;}&.input-group-sm{--input-height:2rem;--input-padding-x:.625rem;--input-font-size:.8125rem;--input-radius:var(--radius-control-sm);--icon-size:.875rem;--icon-offset:-.0625rem;gap:.375rem;}&.input-group-lg{--input-height:2.75rem;--input-padding-x:.875rem;--input-font-size:1rem;--input-radius:var(--radius-control-lg);--icon-size:1.125rem;}/* The browser filled the field in: the whole well takes the tint, not a box inside it. */&:has(.input-group-field:autofill){background:color-mix(in oklab,var(--color-info)7%,var(--color-surface-inset));}/* * Wrap: a field that collects several values. Each value is a chip in the well, the field takes what is * left of the last line, and the well grows by a line when the values need one. A chip sits --input-inset * in from the well's edge and takes the concentric corner. */&.input-group-wrap{--input-inset:6px;flex-wrap:wrap;gap:4px;padding-block:var(--input-inset);&:has(.chip:first-child){padding-inline-start:var(--input-inset);}&.input-group-sm{--input-inset:4px;}.chip{--chip-height:calc(var(--input-height)-var(--input-inset)*2);--chip-padding-x:.5rem;border-radius:calc(var(--input-radius)*var(--corner-scale)-var(--input-inset));corner-shape:var(--corner-shape);}.input-group-field{flex:115rem;align-self:auto;min-height:calc(var(--input-height)-var(--input-inset)*2);}.chip+.input-group-field{margin-inline-start:.25rem;}}/* A button in the well sits 4px in, so its corner is the well's corner minus that inset. */.btn{--btn-height:calc(var(--input-height)-8px);--btn-padding-x:.625rem;--btn-font-size:.78125rem;/* The button multiplies its radius by the corner scale itself, so the inset is divided by it here. */--btn-radius:calc(var(--input-radius)-4px/var(--corner-scale));--btn-lift:0px;--icon-size:.875rem;--icon-offset:-.0625rem;min-height:var(--btn-height);/* A square one stays square on touch, where a button alone would widen to 44px. */&.btn-square{min-width:var(--btn-height);}&:first-child{margin-inline-start:calc(4px-var(--input-padding-x));}&:last-child{margin-inline-end:calc(4px-var(--input-padding-x));}}@media(pointer:coarse){min-height:max(var(--input-height),2.75rem);font-size:max(var(--input-font-size),1rem);/* Every size is 44px on touch, so a button or a chip in the well keeps its inset there. */&:not(.input-group-lg){--input-height:2.75rem;}}@media(forced-colors:active){outline:1pxsolid FieldText;outline-offset:-1px;&:has(.input-group-field:focus){outline:2pxsolid Highlight;outline-offset:-2px;}}}/* The native field inside a group: no material of its own, it fills the well. */.input-group-field{flex:110;align-self:stretch;min-width:0;margin:0;padding:0;border:0;border-radius:0;outline:none;background:transparent;color:var(--color-text-strong);box-shadow:none;font:inherit;font-feature-settings:inherit;caret-color:var(--color-text-strong);appearance:none;&::placeholder{color:var(--color-text-muted);opacity:1;}/* The engine's autofill color never arrives, so the well under the field keeps drawing it. */&:autofill{transition: background-color 0s600000s;}&::-webkit-search-cancel-button{width:14px;height:14px;margin-inline-start:.5rem;background:var(--color-text-muted);mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'%3E%3Cpath d='m4.5 4.5 7 7m0-7-7 7'/%3E%3C/svg%3E")center/containno-repeat;appearance:none;cursor:pointer;}/* The same marks as a plain field draws for a date, a time or a number. */&::-webkit-calendar-picker-indicator{box-sizing:border-box;width:1rem;height:1rem;margin-inline:.5rem0;padding:0;background:var(--color-text-muted);mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 4h9a1 1 0 0 1 1 1v7.5a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM2.5 7h11M5.5 2.5V5M10.5 2.5V5'/%3E%3C/svg%3E")center/containno-repeat;opacity:1;cursor:pointer;}/* The engine pads each part of a date by a pixel, which would make the field taller than its neighbors. */&::-webkit-datetime-edit{margin-block:-1px;}&[type="time"]::-webkit-calendar-picker-indicator{mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2.5a5.5 5.5 0 1 0 0 11a5.5 5.5 0 0 0 0-11M8 5v3l2 1.5'/%3E%3C/svg%3E");}/* The upper half of the mark steps up and the lower half steps down, as the browser's own does. */&::-webkit-inner-spin-button{width:1rem;margin-inline:.5rem0;background:var(--color-text-muted);mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5.5 6.25 2.5-2.5 2.5 2.5M10.5 9.75 8 12.25 5.5 9.75'/%3E%3C/svg%3E")center/1remno-repeat;opacity:1;appearance:none;cursor:pointer;}&:disabled::-webkit-calendar-picker-indicator{background:var(--color-text-disabled);cursor:not-allowed;}&:disabled::-webkit-inner-spin-button{background:var(--color-text-disabled);cursor:not-allowed;}@media(forced-colors:active){&::-webkit-calendar-picker-indicator{background: FieldText;forced-color-adjust:none;}&::-webkit-inner-spin-button{background: FieldText;forced-color-adjust:none;}}&:disabled{color:var(--color-text-disabled);cursor:not-allowed;&::placeholder{color:var(--color-text-disabled);}}}/* Fixed text beside the value, such as a protocol, a unit or a domain. */.input-group-addon{flex:none;color:var(--color-text-muted);white-space:nowrap;user-select:none;-webkit-user-select:none;}/* A <fieldset> that groups related controls under one caption, without the browser's groove. */.fieldset{display:flex;flex-direction:column;gap:.75rem;min-width:0;margin:0;padding:0;border:0;}/* Its <legend>: the group's label, in the same voice as a field's. */.fieldset-legend{margin-bottom:.75rem;padding:0;color:var(--color-text-strong);font-size:.875rem;font-weight:var(--font-weight-control);line-height:1.4;}/* A label stacked over its control, with optional guidance below, all on one edge. */.field{display:grid;gap:.5rem;min-width:0;color:var(--color-text-strong);font-size:.875rem;font-weight:var(--font-weight-control);line-height:1.4;}/* Guidance sits closer to its control than the label does: 6px below against 8px above. */.field-hint,.field-error{margin-top:-.125rem;font-size:.75rem;font-weight:400;line-height:1.5;text-wrap: pretty;}.field-hint{color:var(--color-text-muted);}/* The error appears with the invalid state it explains, and steps aside while the value is valid. */.field-error{color:var(--color-danger);}.field:not(:has([aria-invalid="true"],:user-invalid)).field-error{display:none;}/* While an error shows, it replaces the hint instead of stacking under it. */.field:has([aria-invalid="true"],:user-invalid):has(.field-error).field-hint{display:none;}}