main
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}