https://github.com/whatwg/html/issues/10614, https://github.com/w3c/csswg-drafts/issues/10346
Today it is possible to get Range
information from nodes. The Range
interface specifies a region of text within a node, and can be used to drive Selection and Highlights. It is not possible to use Range
interfaces to select part of an <input>
or <textarea>
s live value.
Range information is useful for two main reasons:
DOMRect
can be useful to anchor popover or other decorative elements to the current selection, the caret, or a specified range.Range
to the CSS highlight API could enable richer inputs without having to go to extreme lengths to style portions of an inputs value.Today, it is not possible to extract Range
information from a <textarea>
or <input>
value. Trying to do so will result in selecting the node representing the initial value of the <textarea>
, or nothing in the case of <input>
elements.
<textarea id="field">This is the textarea initial value</textarea>
<script>
const r = new Range()
r.setStart( field.children[0], 13 )
r.setEnd( field.children[0], 21 )
</script>
The above JavaScript will select the text node that represents the field’s initial value, but not the values current value.
For developers that wish to extract such Range information for the current value, they will need to duplicate the <textarea>
element into a new node, assign the .textContent
to the field .value
, and then extract range information from that:
<textarea id="field">This is the textarea initial value</textarea>
<script>
const r = new Range()
const fakeField = document.createElement('div')
fakeField.textContent = field.value
const styles = getComputedStyle(field);
for (let i = 0; i < styles.length; i += 1) {
const name = styles[i];
fakeField.style[name] = styles[name];
}
field.insertAdjacentElement(fakeField, 'afterend')
r.setStart( fakeField.children[0], 13 )
r.setEnd( fakeField.children[0], 21 )
</script>
This will provide a Range interface that can provide the user some information, such as the bounding DOMRect
, but it is computationally expensive to clone the element and all of its styles simply to get the DOMRect
, and would need to be re-done for any style changes (such as changing writing direction) and yet more work is needed to manage selections correctly.
GitHub’s <text-expander-element>
is a Custom Element built to handle this specific use case, and powers the Emoji, Issue and At-Mention suggestions inside comment fields. In order to do this, it uses an underlying library - dom-input-range which uses the above described technique of copying the textarea in order to extract Rects
.
Here are two possible paths to solving this:
Range
Section titled Expanding%20RangeExpanding the Range()
interface’s .setStart()
and .setEnd()
to select the live value of an <input>
or <textarea>
element.
This would transparently ensure that, when given a <textarea>
or <input>
, setStart()
would use the live value instead of the child nodes (where applicable). This comes with compatibility risks, where existing callsites that pass these values in may result in new selections in surprising ways. Additionally, this opens a potential for composed Ranges which select content adjacent to, as well as inside, the field - which is potentially undesirable and not currently possible today.
Range
typeSection titled Adding%20a%20new%20Range%20typeAdding a new InputRange()
sub-class of Range()
, the exclusive purpose of which is to create ranges on <input>
and <textarea>
elements. The .setStart()
and .setEnd()
methods would only allow <textarea>
or <input>
as a Node, and would otherwise throw an Error.
This would increase the API surface area of the web platform, but on the other hand it side-steps the issues around transparently extending Range. The InputRange()
interface offers a natural boundary layer between other Range
elements and avoids selection compositio issues for APIs where Range
can span multiple nodes. The CSS highlight API, for example, can special case InputRange
. Additionally, a separate interface can side-step potential security issues, for example serialising with .toString()
could throw, the .surroundContents()
function can be elided, and so on.
CSS highlights are a new API to style text ranges in a given node, using the ::highlight
pseudo element. This same API would be very useful for also styling ranges inside of an <input>
or <textarea>
.
<input>
or <textarea>
?Section titled Why%20would%20we%20need%20CSS%20highlights%20in%20%3Cinput%3E%20or%20%3Ctextarea%3E%3FToday, it is a common UI pattern to decorate regions of text within an input for example to suggest potential spelling or gramatical mistakes, syntax highlighting, or to highlight side-effectful regions of a message:
Today, it is not possible to get Range information from input and textareas (see above) and therefore it is also not possible to supply ranges to the CSS highlights API. Unlocking the facility to provide CSS highlights to input ranges would make it much easier to create this kind of UI, which is otherwise very difficult to achieve.
Aside from contenteditable it is possible to overlaying (or underlay) an input with a <div>
; tracking the user’s input, rendering a richer version into the <div>
. GitHub’s search bar does exactly this, setting the color
& background-color
of the <input>
to transparent
which allows sighted users to see the <div>
that is sitting below. As the user inputs text, the <div>
sitting below is populated with a syntax-highlighted variant of the input. This is tricky to get right and remains fragile; the source of many bugs. Correctly tracking composition events, for example, can be challenging (refer to this bug report during a private beta ship of this feature). Additionally there are myriad ways to break the effect, such as forced fonts, text-zoom, or changing writing directions, which all need to be actively mitigated:
Extending the CSS.highlights
API to accepting ranges from input elements (see above proposal on InputRange
) would solve this simply and effectively.
It would also be useful (though not necessary) to extend the CSS highlights API to allow for styling the cursor
, caret-color
& outline
properties in the way that both ::spelling-error
and ::grammar-error
allow today.
A common UI pattern is having “predictive” suggestions that provide the rest of the word or sentence you’re typing. Often these can be contextually aware, for example the predictive model can be programmed around (or trained on) data within the product. Browsers have the facility to enter such predictions based on the operating system and browser combination; for example Apple’s iOS 18 or Microsoft Edge on Windows both offer this, which can be turned off using the writingsuggestions
attribute. However, often sites wish to provide their own suggestions which may be more conextually relevant:
These “Ghost Text” suggestions are not only limited to predictive completions, but can also offer increased usability in aiding a users understanding about how autocomplete options may alter an input value:
This is a common UI pattern and is likely to get more common with the proliferation of LLMs especially as their availability increases in the web platform with APIs such as WebNN or the Writing Assistance APIs.
It’s also a very difficult effect to achieve for multiline inputs, due to the limitations of <textarea>
.
Like the other solutions, its possible to cheat this effect by underlaying/ overlaying an element such as a <div>
and copying over the content. Like the others this comes with the usual problems of tracking styling (such as writing direction), but there are other issues when it comes to multiline input and setting a value:
This is resolvable by editing the input value to add newline characters, but this breaks the user’s undo/redo history, and requires a lot of complex state tracking.
In addition to these various bugs, making an accessible and discoverable interface for suggestions has been challenging.
Extending the HTMLInputElement
and HTMLTextAreaElement
interfaces to include an API to accept and commit suggestions, and have the browser surface this in a manner cohesive to platform conventions would greatly alleviate the amount of engineering effort required to present this UI. A proposed sketch for such an API:
interface SuggestionValueMixin {
undefined suggestValue(DOMString replacementValue);
undefined clearSuggestedValue();
attribute EventHandler onsuggestioncommit;
attribute EventHandler onsuggestiondismiss;
}
HTMLInputElement includes SuggestionValueMixin;
HTMLTextAreaElement includes SuggestionValueMixin;
In addition, it would be useful to have the regions of text that are additions to the text be selectable via a pseudo-element selector, perhaps ::suggestion
or ::suggestion(n)
if multiple regions of suggested text are allowed.
One issue with this API is that suggesting a complete replacement value would mean being able to handle differences such as deletions, which is a far less common UI pattern. Consequently we could look to design an API which inserts only additions at precise locations:
interface Suggestion {
constructor(DOMString insertedValue, int position)
}
interface SuggestionValueMixin {
undefined suggest(Suggestion suggestion);
undefined clearSuggestion(Suggestion suggestion);
attribute EventHandler onsuggestioncommit;
attribute EventHandler onsuggestiondismiss;
}
HTMLInputElement includes SuggestionValueMixin;
HTMLTextAreaElement includes SuggestionValueMixin;
The developer would be expected to know which character position to insert the suggestion, but the upside is that it is much simpler for the developer to anticipate (and for the browser to calculate) where the additions go.
https://github.com/whatwg/html/issues/7794
Input masking is a technique used to separate fragments of a text input into a more recognisable pattern. This makes contiguous strings (such as large numbers or product keys) much easier to read, as they can be formatted to aid in pattern recognition. For example:
4556 6059 2941 7661
).SE1 7PB
),
to separate each set of 3 digits (e.g. 1 000 000 000
).01/01/2025
).(555) 876 5309
)..
s (e.g. 192.168.1.1
).Creating these kinds of masked inputs requires a lot of effort and typically involves combining multiple inputs into one (which can cause issues with copy/paste or deleting inputs), or by injecting characters into inputs (which can cause issues around undo/redo history, and limits the ability to style the masked characters separately from user input).
Many implementations in the wild fail due to usability concerns, or they fail various WCAG critera (some excellent reading material on the topic. Providing a native implementation can alleviate this by creating a simple to use accessible out of the box experience.
Many libraries exist to provide this functionality for the web. A non-comprehensive alphabetical list:
Many spreadsheet applications such as Excel provide built-in input masking.
Other languages, platforms and frameworks provide masking as a UI primitive or have equivalent libraries in their ecosystems, for example .Net’s maskedtextbox.mask
, or the edittext-mask
third party Android library.
Most libraries expose a microsyntax for expressing the presentational characters of the input, vs the characters the user inputs. The majority (Alpine, inputmask, maska, imask, v-mask, jQuery Mask) define the following characters as special:
9
represents an numeric character of the user input (in maska & v-mask this is instead the #
symbol).A
represents an alpha character of the user input (in maska this is instead the @
symbol).*
represents an alphanumeric character of the user input (in v-mask this is instead the N
symbol).Given the above, this means the following examples will work:
999,999,999
is a mask which will allow numeric input, that will add a comma every third digit, so the user inputting 123456789
will result in the input visually displaying 123,456,789
.(999) 999-9999
is a mask allowing numeric telephone number style input, and will add the (
, )
and -
substrings to the input, thus a user inputting 5558765309
will result in a rendered input of (555) 876-5309
.Extending the HTMLInputElement
to include a mask
attribute that allows for a microsyntax featuring the A
, 9
and *
characters seems like a reasonable step forward. Any literal characters will be filled in as the user’s input reaches the required length. The ::masked
or ::masked(n)
pseudo element selector can be used to select the regions of masked text to style appropriately. The input’s value will not change, as the masked characters are presentational only.