Control
Source
Example
html
<div class="x-control">
<input type="text">
</div>More examples can be found in the Winduum docs.
Example CSS
css
@import "winduum/src/components/control/props/color.css" layer(theme);
@import "winduum/src/components/control/props/default.css" layer(theme);
@import "winduum/src/components/control/props/floating.css" layer(theme);
@import "winduum/src/components/control/props/icon.css" layer(theme);
@import "winduum/src/components/control/props/select-picker.css" layer(theme);
@import "winduum/src/components/control/props/select.css" layer(theme);
@import "winduum/src/components/control/color.css" layer(utilities);
@import "winduum/src/components/control/default.css" layer(utilities);
@import "winduum/src/components/control/file.css" layer(utilities);
@import "winduum/src/components/control/floating-interactive.css" layer(utilities);
@import "winduum/src/components/control/floating.css" layer(utilities);
@import "winduum/src/components/control/icon.css" layer(utilities);
@import "winduum/src/components/control/interactive.css" layer(utilities);
@import "winduum/src/components/control/invalid.css" layer(utilities);
@import "winduum/src/components/control/select-multiple.css" layer(utilities);
@import "winduum/src/components/control/select-picker.css" layer(utilities);
@import "winduum/src/components/control/select.css" layer(utilities);
/* Example customization */
@layer theme {
:root, :host {
--x-control-block-size: 2.5rem;
--x-control-block-size-textarea: 2.5rem;
}
}Winduum CSS Code
winduum/src/components/control/props/color.css
css
:root, :host {
--x-control-color-swatch-inline-size: 1.25rem;
--x-control-color-swatch-block-size: 1.25rem;
--x-control-color-swatch-border-radius: var(--radius-full);
}winduum/src/components/control/props/default.css
css
:root, :host {
--x-control-block-size: 3rem;
--x-control-block-size-textarea: 8rem;
--x-control-padding-inline: 0.75rem;
--x-control-padding-block: 0.75rem;
--x-control-border-width: 1px;
--x-control-border-radius: var(--radius-xl);
--x-control-border-color: currentColor;
--x-control-font-weight: var(--font-weight-medium);
--x-control-font-size: var(--text-base);
--x-control-background-color: var(--color-body-primary);
--x-control-color: currentColor;
--x-control-outline-width: 3px;
--x-control-placeholder-color: currentColor;
--x-control-placeholder-opacity: 50%;
}winduum/src/components/control/props/floating.css
css
:root, :host {
--x-control-label-translate-y: 0.625rem;
--x-control-label-scale: 0.8;
}winduum/src/components/control/props/icon.css
css
:root, :host {
--x-control-icon-size: 1.25rem;
--x-control-icon-gap: 0.375rem;
}winduum/src/components/control/props/select-picker.css
css
:root, :host {
--x-control-select-picker-border-width: 1px;
--x-control-select-picker-border-radius: var(--radius-lg);
--x-control-select-picker-border-color: currentColor;
--x-control-select-picker-background-color: var(--color-body-primary);
--x-control-select-picker-margin-block: 0.25rem;
--x-control-select-picker-padding-block: 0.25rem;
--x-control-select-picker-padding-inline: 0.25rem;
--x-control-select-picker-gap: 0.125rem;
--x-control-select-picker-scrollbar-color: currentColor var(--color-body-primary);
--x-control-select-option-padding-block: 0.5rem;
--x-control-select-option-padding-inline: 0.5rem;
--x-control-select-option-font-size: var(--text-sm);
--x-control-select-option-font-weight: var(--font-weight-semibold);
--x-control-select-option-line-height: calc(1em + 0.125rem);
--x-control-select-option-border-radius: var(--radius-md);
--x-control-select-option-hocus-background-color: var(--color-body-secondary);
}winduum/src/components/control/props/select.css
css
:root, :host {
--x-control-select-icon-size: 1.25rem;
--x-control-select-icon-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke="currentColor"><path d="M10.998 15.467c.491.71 1.513.71 2.004 0l3.767-5.453c.581-.843 0-2.013-1.002-2.014H8.234C7.232 8 6.65 9.17 7.231 10.014z" /></svg>');
--x-control-select-icon-margin-inline-end: -0.25rem;
}winduum/src/components/control/color.css
css
.x-control {
> :where(input) {
&::-webkit-color-swatch-wrapper {
inline-size: var(--x-control-color-swatch-inline-size);
block-size: var(--x-control-color-swatch-block-size);
padding: 0;
}
&::-moz-color-swatch {
inline-size: var(--x-control-color-swatch-inline-size);
block-size: var(--x-control-color-swatch-block-size);
border-radius: var(--x-control-color-swatch-border-radius);
margin: 0;
}
&::-webkit-color-swatch {
border-radius: var(--x-control-color-swatch-border-radius);
}
&[type="color"] ~ label {
margin-inline-start: calc(var(--x-control-padding-inline) + var(--x-control-color-swatch-inline-size));
}
}
}winduum/src/components/control/default.css
css
.x-control {
display: grid;
block-size: var(--x-control-block-size);
font-family: var(--x-control-font-family);
font-weight: var(--x-control-font-weight);
font-size: var(--x-control-font-size);
letter-spacing: var(--x-control-letter-spacing);
color: var(--x-control-color);
border-radius: var(--x-control-border-radius);
background-color:
color-mix(
in var(--x-control-background-color-space, srgb),
var(--x-control-background-color) var(--x-control-background-color-opacity, 100%),
var(--x-control-background-color-mix, transparent)
);
border:
var(--x-control-border-width) solid
color-mix(
in var(--x-control-border-color-space, srgb),
var(--x-control-border-color) var(--x-control-border-color-opacity, 15%),
var(--x-control-border-color-mix, transparent)
);
outline:
var(--x-control-outline-width) solid
color-mix(
in var(--x-control-outline-color-space, srgb),
var(--x-control-outline-color, var(--color-accent)) var(--x-control-outline-color-opacity, 0%),
var(--x-control-outline-color-mix, transparent)
);
outline-offset: var(--x-control-outline-offset);
grid-template:
[x-control-start] calc(var(--x-control-padding-block) - var(--x-control-border-width))
[x-control-padding] 1fr
calc(var(--x-control-padding-block) - var(--x-control-border-width)) [x-control-end] /
[x-control-start] var(--x-control-padding-inline)
[x-control-padding] 1fr
var(--x-control-padding-inline) [x-control-end];
&:has(textarea) {
height: auto;
}
> :where(*) {
grid-area: x-control-padding;
align-self: center;
}
> :where(input, textarea, select) {
padding-block: var(--x-control-padding-block-start, var(--x-control-padding-block)) var(--x-control-padding-block-end, var(--x-control-padding-block));
padding-inline: calc(var(--x-control-padding-inline) + var(--x-control-padding-inline-start, 0px)) calc(var(--x-control-padding-inline) + var(--x-control-padding-inline-end, 0px));
grid-area: x-control;
text-overflow: ellipsis;
overflow: clip;
align-self: stretch;
align-items: center;
border-radius: inherit;
background-color: inherit;
&:disabled {
cursor: not-allowed;
}
}
> :where(input, select) {
white-space: nowrap;
}
> :where(input, textarea) {
&::placeholder {
color: var(--x-control-placeholder-color);
opacity: var(--x-control-placeholder-opacity);
}
}
> :where(textarea) {
min-block-size: var(--x-control-block-size-textarea);
resize: vertical;
}
}winduum/src/components/control/file.css
css
.x-control:where(:has([type="file"])) {
overflow: clip;
> :where([type="file"]) {
&::file-selector-button {
all: unset;
background-color:
color-mix(
in var(--x-control-file-background-color-space, srgb),
var(--x-control-file-background-color, currentColor) var(--x-control-file-background-color-opacity, 15%),
var(--x-control-file-background-color-mix, transparent)
);
block-size: var(--x-control-block-size);
padding-inline: var(--x-control-padding-inline);
margin-inline: calc(var(--x-control-padding-inline) * -1) var(--x-control-padding-inline);
margin-block-start: calc(var(--x-control-padding-block) * -1);
border-start-end-radius: var(--x-control-border-radius);
border-end-end-radius: var(--x-control-border-radius);
transition: var(--transition-background);
cursor: var(--cursor-pointer, pointer);
}
&:is(:hover, :focus) {
--x-control-file-background-color-opacity: 20%;
}
}
}winduum/src/components/control/floating-interactive.css
css
.x-control:where(:has(:not([type="color"]) ~ label)) {
--x-control-label-focus-opacity: 50%;
--x-control-placeholder-color: transparent;
> :where(input, textarea, select) {
&:is(:focus, :not(:placeholder-shown)) {
~ label {
transform: translateY(calc(var(--x-control-label-translate-y) * -1)) scale(var(--x-control-label-scale));
opacity: var(--x-control-label-focus-opacity);
}
}
}
}winduum/src/components/control/floating.css
css
.x-control:where(:has(:not([type="color"]) ~ label)) {
> :where(label) {
margin-inline: var(--x-control-padding-inline-start, 0) var(--x-control-padding-inline-end, 0);
transition: var(--transition-transform), var(--transition-color), var(--transition-opacity);
pointer-events: none;
transform-origin: 0 50%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: clip;
will-change: transform;
}
> :where(textarea) {
padding-block-start: calc(var(--x-control-padding-block) + var(--x-control-label-translate-y) * var(--x-control-label-scale));
+ :where(label) {
align-self: start;
}
}
> :where(input, select) {
padding-block: calc(var(--x-control-padding-block) + var(--x-control-label-translate-y) * var(--x-control-label-scale)) calc(var(--x-control-padding-block) - var(--x-control-label-translate-y) * var(--x-control-label-scale));
&::-webkit-calendar-picker-indicator,
&::-webkit-search-cancel-button,
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
transform: translateY(calc(var(--x-control-label-translate-y) * var(--x-control-label-scale) * -1));
}
}
&:where(:has(:required)) :where(label)::after {
color: var(--x-control-required-color, var(--color-error));
content: " *";
}
}winduum/src/components/control/icon.css
css
.x-control {
--x-control-start: calc(var(--x-control-icon-count-start, 1) * (var(--x-control-icon-size) + var(--x-control-icon-gap)) - var(--x-control-icon-gap));
--x-control-end: calc(var(--x-control-icon-count-end, 1) * (var(--x-control-icon-size) + var(--x-control-icon-gap)) - var(--x-control-icon-gap));
&:has(textarea) {
:where(.me-auto), :where(.ms-auto) {
align-self: start;
}
}
&:has(.me-auto) {
--x-control-padding-inline-start: calc(var(--x-control-start) + var(--x-control-padding-inline));
}
&:has(.ms-auto) {
--x-control-padding-inline-end: calc(var(--x-control-end) + var(--x-control-padding-inline));
}
&:has(.me-auto > *:nth-child(2)) {
--x-control-icon-count-start: 2;
}
&:has(.ms-auto > *:nth-child(2)) {
--x-control-icon-count-end: 2;
}
:where(.me-auto), :where(.ms-auto) {
gap: var(--x-control-icon-gap);
display: flex;
align-items: center;
}
}winduum/src/components/control/interactive.css
css
.x-control {
transition-property: var(--default-transition-property);
transition-timing-function: var(--ease-in-out);
transition-duration: var(--default-transition-duration);
&:focus-within {
--x-control-border-color: var(--x-control-focus-border-color, var(--color-accent));
--x-control-border-color-opacity: var(--x-control-focus-border-color-opacity, 100%);
--x-control-outline-offset: var(--x-control-focus-outline-offset);
--x-control-outline-color-opacity: var(--x-control-focus-outline-color-opacity, 20%);
}
&.disabled, &:has(:where(input, textarea, select):disabled) {
--x-control-background-color: var(--x-control-disabled-background-color, currentColor);
--x-control-background-color-opacity: var(--x-control-disabled-background-color-opacity, 5%);
--x-control-border-color: var(--x-control-disabled-border-color, currentColor);
--x-control-border-color-opacity: var(--x-control-disabled-border-color-opacity);
}
}winduum/src/components/control/invalid.css
css
.x-control {
&:has(:user-invalid) {
--x-control-background-color: var(--x-control-invalid-background-color, var(--color-error));
--x-control-background-color-opacity: var(--x-control-invalid-background-color-opacity, 10%);
--x-control-border-color: var(--x-control-invalid-border-color, var(--color-error));
--x-control-border-color-opacity: var(--x-control-invalid-border-color-opacity, 100%);
--x-control-outline-color: var(--x-control-invalid-outline-color, var(--color-error));
--x-control-color: var(--x-control-invalid-color, var(--color-error));
}
}winduum/src/components/control/select-multiple.css
css
.x-control:has(select[multiple]) {
--x-control-block-size: auto;
select {
overflow-y: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
}winduum/src/components/control/select-picker.css
css
.x-control:has(select:not([multiple])) {
&::after {
will-change: transform;
transition: var(--transition-transform);
}
&:has(select:open) {
&::after {
transform: rotate(180deg);
}
}
select {
&, &::picker(select) {
appearance: base-select;
}
&::picker-icon {
display: none;
}
&::picker(select) {
border-radius: var(--x-control-select-picker-border-radius);
border:
var(--x-control-select-picker-border-width) solid
color-mix(
in var(--x-control-select-picker-border-color-space, srgb),
var(--x-control-select-picker-border-color) var(--x-control-select-picker-border-color-opacity, 15%),
var(--x-control-select-picker-border-color-mix, transparent)
);
background-color: var(--x-control-select-picker-background-color);
margin-block: var(--x-control-select-picker-margin-block);
padding-block: var(--x-control-select-picker-padding-block);
padding-inline: var(--x-control-select-picker-padding-inline);
gap: var(--x-control-select-picker-gap);
display: flex;
flex-direction: column;
cursor: auto;
transform-origin: top center;
scrollbar-width: thin;
scrollbar-color: var(--x-control-select-picker-scrollbar-color);
will-change: transform, opacity;
transition:
opacity var(--default-transition-duration) var(--ease-in-out),
transform var(--default-transition-duration) var(--ease-out),
display var(--default-transition-duration) allow-discrete,
overlay var(--default-transition-duration) allow-discrete;
}
&:not(:open)::picker(select) {
opacity: 0%;
transform: scale(0.9);
display: revert;
}
&:open::picker(select) {
opacity: 100%;
transform: scale(1);
@starting-style {
opacity: 0%;
transform: scale(0.95);
}
}
option {
padding-block: var(--x-control-select-option-padding-block);
padding-inline: var(--x-control-select-option-padding-inline);
font-size: var(--x-control-select-option-font-size);
font-weight: var(--x-control-select-option-font-weight);
line-height: var(--x-control-select-option-line-height);
border-radius: var(--x-control-select-option-border-radius);
transition: var(--transition-background), var(--transition-color);
&::checkmark {
display: none;
}
&:hover, &:focus-visible {
background-color: var(--x-control-select-option-hocus-background-color);
}
&:checked {
color: var(--x-control-select-option-checked-color, var(--color-accent));
background-color:
color-mix(
in var(--x-control-select-option-checked-background-color-space, srgb),
var(--x-control-select-option-checked-background-color, var(--color-accent)) var(--x-control-select-option-checked-background-color-opacity, 10%),
var(--x-control-select-option-checked-background-color-mix, transparent)
);
}
}
}
}winduum/src/components/control/select.css
css
.x-control:has(select:not([multiple])) {
--x-control-padding-inline-end: calc(var(--x-control-end) + var(--x-control-padding-inline) + var(--x-control-select-icon-margin-inline-end));
&:has(.ms-auto) {
--x-control-icon-count-end: 2;
}
&::after {
inline-size: var(--x-control-select-icon-size);
block-size: var(--x-control-select-icon-size);
mask: var(--x-control-select-icon-mask);
margin-inline: auto var(--x-control-select-icon-margin-inline-end);
grid-area: x-control-padding;
background-color: currentColor;
pointer-events: none;
align-self: center;
content: "";
}
:where(.ms-auto) {
margin-inline-end: var(--x-control-select-icon-size);
}
:where(select, option) {
cursor: var(--cursor-pointer, pointer);
}
}Stimulus Actions
stepDown
Triggers native stepDown method on input.
Example
html
<div class="x-control" data-controller="x-control">
<input type="number" min="0" value="10">
<div class="me-auto">
<svg role="button" data-action="click->x-control#stepDown" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
</svg>
</div>
</div>stepUp
Triggers native stepUp method on input.
Example
html
<div class="x-control" data-controller="x-control">
<input type="number" min="0" value="0">
<div class="ms-auto">
<svg role="button" data-action="click->x-control#stepUp" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</div>
</div>showPicker
Triggers native showPicker method on input.
Example
html
<div class="x-control" data-controller="x-control">
<input type="date">
<div class="ms-auto">
<svg class="size-6" role="button" data-action="click->x-control#showPicker" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />
</svg>
</div>
</div>