main
Raw Download raw file
  1/*
  2 * noVNC general input element CSS
  3 * Copyright (C) 2025 The noVNC authors
  4 * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
  5 * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
  6 */
  7
  8/* ------- SHARED BETWEEN INPUT ELEMENTS -------- */
  9
 10input,
 11textarea,
 12button,
 13select,
 14input::file-selector-button {
 15    padding: 0.5em var(--input-xpadding);
 16    border-radius: 6px;
 17    appearance: none;
 18    text-overflow: ellipsis;
 19
 20    /* Respect standard font settings */
 21    font: inherit;
 22    line-height: 1.6;
 23}
 24input:disabled,
 25textarea:disabled,
 26button:disabled,
 27select:disabled,
 28label[disabled] {
 29    opacity: 0.4;
 30}
 31
 32input:focus-visible,
 33textarea:focus-visible,
 34button:focus-visible,
 35select:focus-visible,
 36input:focus-visible::file-selector-button {
 37    outline: 2px solid var(--novnc-lightblue);
 38    outline-offset: 1px;
 39}
 40
 41/* ------- TEXT INPUT -------- */
 42
 43input:not([type]),
 44input[type=date],
 45input[type=datetime-local],
 46input[type=email],
 47input[type=month],
 48input[type=number],
 49input[type=password],
 50input[type=search],
 51input[type=tel],
 52input[type=text],
 53input[type=time],
 54input[type=url],
 55input[type=week],
 56textarea {
 57    border: 1px solid var(--novnc-lightgrey);
 58    /* Account for borders on text inputs, buttons dont have borders */
 59    padding: calc(0.5em - 1px) var(--input-xpadding);
 60}
 61input:not([type]):focus-visible,
 62input[type=date]:focus-visible,
 63input[type=datetime-local]:focus-visible,
 64input[type=email]:focus-visible,
 65input[type=month]:focus-visible,
 66input[type=number]:focus-visible,
 67input[type=password]:focus-visible,
 68input[type=search]:focus-visible,
 69input[type=tel]:focus-visible,
 70input[type=text]:focus-visible,
 71input[type=time]:focus-visible,
 72input[type=url]:focus-visible,
 73input[type=week]:focus-visible,
 74textarea:focus-visible {
 75    outline-offset: -1px;
 76}
 77
 78textarea {
 79    margin: unset; /* Remove Firefox's built in margin */
 80    /* Prevent layout from shifting when scrollbars show */
 81    scrollbar-gutter: stable;
 82    /* Make textareas show at minimum one line. This does not work when
 83       using box-sizing border-box, in which case, vertical padding and
 84       border width needs to be taken into account. */
 85    min-height: 1lh;
 86    vertical-align: baseline; /* Firefox gives "text-bottom" by default */
 87}
 88
 89/* ------- NUMBER PICKERS ------- */
 90
 91/* We can't style the number spinner buttons:
 92   https://github.com/w3c/csswg-drafts/issues/8777 */
 93input[type=number]::-webkit-inner-spin-button,
 94input[type=number]::-webkit-outer-spin-button {
 95    /* Get rid of increase/decrease buttons in WebKit */
 96    appearance: none;
 97}
 98input[type=number] {
 99    /* Get rid of increase/decrease buttons in Firefox */
100    appearance: textfield;
101}
102
103/* ------- BUTTON ACTIVATIONS -------- */
104
105/* A color overlay that depends on the activation level. The level can then be
106   set for different states on an element, for example hover and click on a
107   <button>. */
108input, button, select, option,
109input::file-selector-button,
110.button-activations {
111    --button-activation-level: 0;
112    /* Note that CSS variables aren't functions, beware when inheriting */
113    --button-activation-alpha: calc(0.08 * var(--button-activation-level));
114    /* FIXME: We want the image() function instead of the linear-gradient()
115              function below. But it's not supported in the browsers yet. */
116    --button-activation-overlay:
117        linear-gradient(rgba(0, 0, 0, var(--button-activation-alpha))
118        100%, transparent);
119    --button-activation-overlay-light:
120        linear-gradient(rgba(255, 255, 255, calc(0.23 * var(--button-activation-level)))
121        100%, transparent);
122}
123.button-activations {
124    background-image: var(--button-activation-overlay);
125
126    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
127    -webkit-tap-highlight-color: transparent;
128}
129/* When we want the light overlay on activations instead.
130   This is best used on elements with darker backgrounds. */
131.button-activations.light-overlay {
132    background-image: var(--button-activation-overlay-light);
133    /* Can't use the normal blend mode since that gives washed out colors. */
134    /* FIXME: For elements with these activation overlays we'd like only
135              the luminosity to change. The proprty "background-blend-mode" set
136              to "luminosity" sounds good, but it doesn't work as intended,
137              see: https://bugzilla.mozilla.org/show_bug.cgi?id=1806417 */
138    background-blend-mode: overlay;
139}
140
141input:hover, button:hover, select:hover, option:hover,
142input::file-selector-button:hover,
143.button-activations:hover {
144    --button-activation-level: 1;
145}
146/* Unfortunately we have to disable the :hover effect on touch devices,
147   otherwise the style lingers after tapping the button. */
148@media (any-pointer: coarse) {
149    input:hover, button:hover, select:hover, option:hover,
150    input::file-selector-button:hover,
151    .button-activations:hover {
152        --button-activation-level: 0;
153    }
154}
155input:active, button:active, select:active, option:active,
156input::file-selector-button:active,
157.button-activations:active {
158    --button-activation-level: 2;
159}
160input:disabled, button:disabled, select:disabled, select:disabled option,
161input:disabled::file-selector-button,
162.button-activations:disabled {
163    --button-activation-level: 0;
164}
165
166/* ------- BUTTONS -------- */
167
168input[type=button],
169input[type=color],
170input[type=image],
171input[type=reset],
172input[type=submit],
173input::file-selector-button,
174button,
175select {
176    min-width: 8em;
177    border: none;
178    color: black;
179    font-weight: bold;
180    background-color: var(--novnc-buttongrey);
181    background-image: var(--button-activation-overlay);
182    cursor: pointer;
183    /* Disable Chrome's touch tap highlight */
184    -webkit-tap-highlight-color: transparent;
185}
186input[type=button]:disabled,
187input[type=color]:disabled,
188input[type=image]:disabled,
189input[type=reset]:disabled,
190input[type=submit]:disabled,
191input:disabled::file-selector-button,
192button:disabled,
193select:disabled {
194    /* See Firefox bug:
195       https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */
196    cursor: default;
197}
198
199input[type=button],
200input[type=color],
201input[type=reset],
202input[type=submit] {
203    /* Workaround for text-overflow bugs in Firefox and Chromium:
204        https://bugzilla.mozilla.org/show_bug.cgi?id=1800077
205        https://bugs.chromium.org/p/chromium/issues/detail?id=1383144 */
206    overflow: clip;
207}
208
209/* ------- COLOR PICKERS ------- */
210
211input[type=color] {
212    min-width: unset;
213    box-sizing: content-box;
214    width: 1.4em;
215    height: 1.4em;
216}
217input[type=color]::-webkit-color-swatch-wrapper {
218    padding: 0;
219}
220/* -webkit-color-swatch & -moz-color-swatch cant be in a selector list:
221   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
222input[type=color]::-webkit-color-swatch {
223    border: none;
224    border-radius: 6px;
225}
226input[type=color]::-moz-color-swatch {
227    border: none;
228    border-radius: 6px;
229}
230
231/* -- SHARED BETWEEN CHECKBOXES, RADIOBUTTONS AND THE TOGGLE CLASS -- */
232
233input[type=radio],
234input[type=checkbox] {
235    display: inline-flex;
236    justify-content: center;
237    align-items: center;
238    background-color: var(--novnc-buttongrey);
239    background-image: var(--button-activation-overlay);
240    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
241    -webkit-tap-highlight-color: transparent;
242    width: 16px;
243    --checkradio-height: 16px;
244    height: var(--checkradio-height);
245    padding: 0;
246    margin: 0 6px 0 0;
247    /* Don't have transitions for outline in order to be consistent
248       with other elements */
249    transition: all 0.2s, outline-color 0s, outline-offset 0s;
250
251    /* A transparent outline in order to work around a graphical clipping issue
252       in WebKit. See bug: https://bugs.webkit.org/show_bug.cgi?id=256003 */
253    outline: 1px solid transparent;
254    position: relative; /* Since ::before & ::after are absolute positioned */
255
256    /* We want to align with the middle of capital letters, this requires
257       a workaround. The default behavior is to align the bottom of the element
258       on top of the text baseline, this is too far up.
259       We want to push the element down half the difference in height between
260       it and a capital X. In our font, the height of a capital "X" is 0.698em.
261     */
262    vertical-align: calc(0px - (var(--checkradio-height) - 0.698em) / 2);
263    /* FIXME: Could write 1cap instead of 0.698em, but it's only supported in
264              Firefox as of 2023 */
265    /* FIXME: We probably want to use round() here, see bug 8148 */
266}
267input[type=radio]:focus-visible,
268input[type=checkbox]:focus-visible {
269    outline-color: var(--novnc-lightblue);
270}
271input[type=checkbox]::before,
272input[type=checkbox]:not(.toggle)::after,
273input[type=radio]::before,
274input[type=radio]::after {
275    content: "";
276    display: block; /* width & height doesn't work on inline elements */
277    transition: inherit;
278    /* Let's prevent the pseudo-elements from taking up layout space so that
279       the ::before and ::after pseudo-elements can be in the same place. This
280       is also required for vertical-align: baseline to work like we want it to
281       on radio/checkboxes. If the pseudo-elements take up layout space, the
282       baseline of text inside them will be used instead. */
283    position: absolute;
284}
285input[type=checkbox]:not(.toggle)::after,
286input[type=radio]::after {
287    width: 10px;
288    height: 2px;
289    background-color: transparent;
290    border-radius: 2px;
291}
292
293/* ------- CHECKBOXES ------- */
294
295input[type=checkbox]:not(.toggle) {
296    border-radius: 4px;
297}
298input[type=checkbox]:not(.toggle):checked,
299input[type=checkbox]:not(.toggle):indeterminate {
300    background-color: var(--novnc-blue);
301    background-image: var(--button-activation-overlay-light);
302    background-blend-mode: overlay;
303}
304input[type=checkbox]:not(.toggle)::before {
305    width: 25%;
306    height: 55%;
307    border-style: solid;
308    border-color: transparent;
309    border-width: 0 2px 2px 0;
310    border-radius: 1px;
311    transform: translateY(-1px) rotate(35deg);
312}
313input[type=checkbox]:not(.toggle):checked::before {
314    border-color: white;
315}
316input[type=checkbox]:not(.toggle):indeterminate::after {
317    background-color: white;
318}
319
320/* ------- RADIO BUTTONS ------- */
321
322input[type=radio] {
323    border-radius: 50%;
324    border: 1px solid transparent; /* To ensure a smooth transition */
325}
326input[type=radio]:checked {
327    border: 4px solid var(--novnc-blue);
328    background-color: white;
329    /* button-activation-overlay should be removed from the radio
330       element to not interfere with button-activation-overlay-light
331       that is set on the ::before element. */
332    background-image: none;
333}
334input[type=radio]::before {
335    width: inherit;
336    height: inherit;
337    border-radius: inherit;
338    /* We can achieve the highlight overlay effect on border colors by
339       setting button-activation-overlay-light on an element that stays
340       on top (z-axis) of the element with a border. */
341    background-image: var(--button-activation-overlay-light);
342    mix-blend-mode: overlay;
343    opacity: 0;
344}
345input[type=radio]:checked::before {
346    opacity: 1;
347}
348input[type=radio]:indeterminate::after {
349    background-color: black;
350}
351
352/* ------- TOGGLE SWITCHES ------- */
353
354/* These are meant to be used instead of checkboxes in some cases. If all of
355   the following critera are true you should use a toggle switch:
356
357    * The choice is a simple ON/OFF or ENABLE/DISABLE
358    * The choice doesn't give the feeling of "I agree" or "I confirm"
359    * There are not multiple related & grouped options
360 */
361
362input[type=checkbox].toggle {
363    display: inline-block;
364    --checkradio-height: 18px; /* Height value used in calc, see above */
365    width: 31px;
366    cursor: pointer;
367    user-select: none;
368    -webkit-user-select: none;
369    border-radius: 9px;
370}
371input[type=checkbox].toggle:disabled {
372    cursor: default;
373}
374input[type=checkbox].toggle:indeterminate {
375    background-color: var(--novnc-buttongrey);
376    background-image: var(--button-activation-overlay);
377}
378input[type=checkbox].toggle:checked {
379    background-color: var(--novnc-blue);
380    background-image: var(--button-activation-overlay-light);
381    background-blend-mode: overlay;
382}
383input[type=checkbox].toggle::before {
384    --circle-diameter: 10px;
385    --circle-offset: 4px;
386    width: var(--circle-diameter);
387    height: var(--circle-diameter);
388    top: var(--circle-offset);
389    left: var(--circle-offset);
390    background: white;
391    border-radius: 6px;
392}
393input[type=checkbox].toggle:checked::before {
394    left: calc(100% - var(--circle-offset) - var(--circle-diameter));
395}
396input[type=checkbox].toggle:indeterminate::before {
397    left: calc(50% - var(--circle-diameter) / 2);
398}
399
400/* ------- RANGE SLIDERS ------- */
401
402input[type=range] {
403    border: unset;
404    border-radius: 8px;
405    height: 15px;
406    padding: 0;
407    background: transparent;
408    /* Needed to get properly rounded corners on -moz-range-progress
409       when the thumb is all the way to the right. Without overflow
410       hidden, the pointy edges of the progress track shows to the
411       right of the thumb. */
412    overflow: hidden;
413}
414@supports selector(::-webkit-slider-thumb) {
415    input[type=range] {
416        /* Needs a fixed width to match clip-path */
417        width: 125px;
418        /* overflow: hidden is not ideal for hiding the left part of the box
419           shadow of -webkit-slider-thumb since it doesn't match the smaller
420           border-radius of the progress track. The below clip-path has two
421           circular sides to make the ends of the track have correctly rounded
422           corners. The clip path shape looks something like this:
423
424                  +-------------------------------+
425              /---|                               |---\
426             |                                         |
427              \---|                               |---/
428                  +-------------------------------+
429
430           The larger middle part of the clip path is made to have room for the
431           thumb. By using margins on the track, we prevent the thumb from
432           touching the ends of the track.
433         */
434        clip-path: path(' \
435         M 4.5 3 \
436         L 4.5 0 \
437         L 120.5 0 \
438         L 120.5 3 \
439         A 1 1 0 0 1 120.5 12 \
440         L 120.5 15 \
441         L 4.5 15 \
442         L 4.5 12 \
443         A 1 1 0 0 1 4.5 3 \
444        ');
445    }
446}
447input[type=range]:hover {
448    cursor: grab;
449}
450input[type=range]:active {
451    cursor: grabbing;
452}
453input[type=range]:disabled {
454    cursor: default;
455}
456input[type=range]:focus-visible {
457    clip-path: none; /* Otherwise it hides the outline */
458}
459/* -webkit-slider.. & -moz-range.. cant be in selector lists:
460   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
461input[type=range]::-webkit-slider-runnable-track {
462    background-color: var(--novnc-buttongrey);
463    height: 7px;
464    border-radius: 4px;
465    margin: 0 3px;
466}
467input[type=range]::-moz-range-track {
468    background-color: var(--novnc-buttongrey);
469    height: 7px;
470    border-radius: 4px;
471}
472input[type=range]::-moz-range-progress {
473    background-color: var(--novnc-blue);
474    height: 9px;
475    /* Needs rounded corners only on the left side. Otherwise the rounding of
476       the progress track starts before the thumb, when the thumb is close to
477       the left edge. */
478    border-radius: 5px 0 0 5px;
479}
480input[type=range]::-webkit-slider-thumb {
481    appearance: none;
482    width: 15px;
483    height: 15px;
484    border-radius: 50%;
485    background-color: white;
486    background-image: var(--button-activation-overlay);
487    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
488    -webkit-tap-highlight-color: transparent;
489    border: 3px solid var(--novnc-blue);
490    margin-top: -4px; /* (track height / 2) - (thumb height /2) */
491
492    /* Since there is no way to style the left part of the range track in
493       webkit, we add a large shadow (1000px wide) to the left of the thumb and
494       then crop it with a clip-path shaped like this:
495                              ___
496        +-------------------/     \
497        |      progress     |Thumb|
498        +-------------------\ ___ /
499
500        The large left part of the shadow is clipped by another clip-path on on
501        the main range input element. */
502    /* FIXME: We can remove the box shadow workaround when this is standardized:
503              https://github.com/w3c/csswg-drafts/issues/4410 */
504
505    box-shadow: calc(-100vw - 8px) 0 0 100vw var(--novnc-blue);
506    clip-path: path(' \
507     M -1000 3 \
508     L 3 3 \
509     L 15 7.5 \
510     A 1 1 0 0 1 0 7.5 \
511     A 1 1 0 0 1 15 7.5 \
512     L 3 12 \
513     L -1000 12 Z \
514    ');
515}
516input[type=range]::-moz-range-thumb {
517    appearance: none;
518    width: 15px;
519    height: 15px;
520    border-radius: 50%;
521    box-sizing: border-box;
522    background-color: white;
523    background-image: var(--button-activation-overlay);
524    border: 3px solid var(--novnc-blue);
525    margin-top: -7px;
526}
527
528/* ------- FILE CHOOSERS ------- */
529
530input[type=file] {
531    background-image: none;
532    border: none;
533}
534input::file-selector-button {
535    margin-right: 6px;
536}
537input[type=file]:focus-visible {
538    outline: none; /* We outline the button instead of the entire element */
539}
540
541/* ------- SELECT BUTTONS ------- */
542
543select {
544    --select-arrow: url('data:image/svg+xml;utf8, \
545        <svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
546             xmlns="http://www.w3.org/2000/svg"> \
547            <path d="m10.5.5-5 5-5-5" fill="none" \
548                  stroke="black" stroke-width="1.5" \
549                  stroke-linecap="round" stroke-linejoin="round"/> \
550        </svg>');
551
552    /* FIXME: A bug in Firefox, requires a workaround for the background:
553              https://bugzilla.mozilla.org/show_bug.cgi?id=1810958 */
554    /* The dropdown list will show the select element's background above and
555       below the options in Firefox. We want the entire dropdown to be white. */
556    background-color: white;
557    /* However, we don't want the select element to actually show a white
558       background, so let's place a gradient above it with the color we want. */
559    --grey-background: linear-gradient(var(--novnc-buttongrey) 100%,
560                                       transparent);
561    background-image:
562        var(--select-arrow),
563        var(--button-activation-overlay),
564        var(--grey-background);
565    background-position: calc(100% - var(--input-xpadding)), left top, left top;
566    background-repeat: no-repeat;
567    padding-right: calc(2*var(--input-xpadding) + 11px);
568    overflow: auto;
569}
570/* FIXME: :active isn't set when the <select> is opened in Firefox:
571          https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */
572select:active {
573    /* Rotated arrow */
574    background-image: url('data:image/svg+xml;utf8, \
575        <svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
576             xmlns="http://www.w3.org/2000/svg" transform="rotate(180)"> \
577            <path d="m10.5.5-5 5-5-5" fill="none" \
578                  stroke="black" stroke-width="1.5" \
579                  stroke-linecap="round" stroke-linejoin="round"/> \
580        </svg>'),
581        var(--button-activation-overlay),
582        var(--grey-background);
583}
584select:disabled {
585    background-image:
586        var(--select-arrow),
587        var(--grey-background);
588}
589/* Note that styling for <option> doesn't work in all browsers
590   since its often drawn directly by the OS. We are generally very
591   limited in what we can change here. */
592option {
593    /* Prevent Chrome from inheriting background-color from the <select> */
594    background-color: white;
595    color: black;
596    font-weight: normal;
597    background-image: var(--button-activation-overlay);
598}
599option:checked {
600    background-color: var(--novnc-lightgrey);
601}
602/* Change the look when the <select> isn't used as a dropdown. When "size"
603   or "multiple" are set, these elements behaves more like lists. */
604select[size]:not([size="1"]), select[multiple] {
605    background-color: white;
606    background-image: unset; /* Don't show the arrow and other gradients */
607    border: 1px solid var(--novnc-lightgrey);
608    padding: 0;
609    font-weight: normal; /* Without this, options get bold font in WebKit. */
610
611    /* As an exception to the "list"-look, multi-selects in Chrome on Android,
612       and Safari on iOS, are unfortunately designed to be shown as a single
613       line. We can mitigate this inconsistency by at least fixing the height
614       here. By setting a min-height that matches other input elements, it
615       doesn't look too much out of place:
616         (1px border * 2) + (6.5px padding * 2) + 24px line-height = 39px */
617    min-height: 39px;
618}
619select[size]:not([size="1"]):focus-visible,
620select[multiple]:focus-visible {
621    /* Text input style focus-visible highlight */
622    outline-offset: -1px;
623}
624select[size]:not([size="1"]) option, select[multiple] option {
625    overflow: hidden;
626    text-overflow: ellipsis;
627    padding: 4px var(--input-xpadding);
628}