Define the color palette in oklch() (#3043)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-09-13 00:56:36 +02:00
1 parent 0fe02b3684
commit 107e493275
28 files changed
+224 -463

No files matched your search

+1 -1
View File
@@ -50,7 +50,7 @@ switchesTriggerList.map(function (switchTriggerEl: HTMLElement) {
- The attribute API stays `data-bs-*`. Renaming it to `data-tblr-*` is a breaking change held for 2.0 — do not start it here.
- Keep the accessible state in sync with the class (`aria-pressed`, `aria-expanded`), as above.
- A new module must be imported from `js/tabler.ts`, or it never ships.
- Shared helpers go to `js/src/tabler.ts` (`prefix`, `hexToRgba`, `getColor`), which reads theme values through `--tblr-*` custom properties.
- There is no shared colour helper: demo charts resolve `--tblr-*` colours to hex in `shared/components/demo/Chart.astro`.
## 4. Docs markers
+6
View File
@@ -0,0 +1,6 @@
---
"@tabler/core": minor
"@tabler/docs": patch
---
Updated the color palette to `oklch()`, masked the checkbox, switch, toggler and carousel icons, and removed the `tabler` JS namespace.
-17
View File
@@ -1,17 +0,0 @@
export const prefix: string = 'tblr-'
export const hexToRgba = (hex: string, opacity: number): string | null => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${opacity})` : null
}
export const getColor = (color: string, opacity: number = 1): string | null => {
const c = getComputedStyle(document.body).getPropertyValue(`--${prefix}${color}`).trim()
if (opacity !== 1) {
return hexToRgba(c, opacity)
}
return c
}
-3
View File
@@ -12,6 +12,3 @@ import './src/sortable'
// Re-export everything from bootstrap.ts (single source of truth)
export * from './src/bootstrap'
// Re-export tabler namespace
export * as tabler from './src/tabler'
+4 -4
View File
@@ -178,19 +178,19 @@
},
{
"path": "./dist/css/tabler-themes.css",
"maxSize": "1.7 kB"
"maxSize": "2 kB"
},
{
"path": "./dist/css/tabler-themes.min.css",
"maxSize": "1.4 kB"
"maxSize": "1.6 kB"
},
{
"path": "./dist/css/tabler-themes.rtl.css",
"maxSize": "1.7 kB"
"maxSize": "2 kB"
},
{
"path": "./dist/css/tabler-themes.rtl.min.css",
"maxSize": "1.4 kB"
"maxSize": "1.6 kB"
},
{
"path": "./dist/css/tabler-vendors.css",
+3 -1
View File
@@ -9,7 +9,9 @@
// Prefixes
// Characters which are escaped by the escape-svg function
$escaped-characters: (('<', '%3c'), ('>', '%3e'), ('#', '%23'), ('(', '%28'), (')', '%29')) !default;
// `%` goes first so the escapes added below are not re-encoded; it shows up in
// `oklch()` lightness values that end up inside svg data uris.
$escaped-characters: (('%', '%25'), ('<', '%3c'), ('>', '%3e'), ('#', '%23'), ('(', '%28'), (')', '%29')) !default;
$black: #000 !default;
$white: #fff !default;
+1 -3
View File
@@ -73,9 +73,7 @@ $mark-bg-dark: mix-color-css(var(--black), var(--yellow), 60%) !default;
$form-select-indicator-color-dark: $body-color-dark !default;
$form-select-indicator-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color-dark}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") !default;
// Compile-time `rgba()` on purpose: url-encoded into the svg below, where `var()` cannot resolve.
$form-switch-color-dark: rgba($white, 0.25) !default;
$form-switch-bg-image-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color-dark}'/></svg>") !default;
$form-switch-color-dark: color-transparent(var(--white), 0.25) !default;
// Form validation colors dark
$form-valid-color-dark: mix-color-css(var(--white), var(--green), 40%) !default;
+45 -50
View File
@@ -34,17 +34,17 @@ $enable-important-utilities: true !default;
$color-mode-type: data !default;
// Colors
$gray-50: #f9fafb !default;
$gray-100: #f3f4f6 !default;
$gray-200: #e5e7eb !default;
$gray-300: #d1d5db !default;
$gray-400: #9ca3af !default;
$gray-500: #6b7280 !default;
$gray-600: #4b5563 !default;
$gray-700: #374151 !default;
$gray-800: #1f2937 !default;
$gray-900: #111827 !default;
$gray-950: #030712 !default;
$gray-50: oklch(98.46% 0.0017 247.84deg) !default; // #f9fafb
$gray-100: oklch(96.7% 0.0029 264.54deg) !default; // #f3f4f6
$gray-200: oklch(92.76% 0.0058 264.53deg) !default; // #e5e7eb
$gray-300: oklch(87.17% 0.0093 258.34deg) !default; // #d1d5db
$gray-400: oklch(71.37% 0.0192 261.32deg) !default; // #9ca3af
$gray-500: oklch(55.1% 0.0234 264.36deg) !default; // #6b7280
$gray-600: oklch(44.61% 0.0263 256.8deg) !default; // #4b5563
$gray-700: oklch(37.29% 0.0306 259.73deg) !default; // #374151
$gray-800: oklch(27.81% 0.0296 256.85deg) !default; // #1f2937
$gray-900: oklch(21.01% 0.0318 264.66deg) !default; // #111827
$gray-950: oklch(12.96% 0.0274 261.69deg) !default; // #030712
// $black and $white moved to `_settings.scss`
@@ -63,18 +63,18 @@ $grays: (
'900': $gray-900,
) !default;
$blue: #066fd1 !default;
$azure: #4299e1 !default;
$indigo: #4263eb !default;
$purple: #ae3ec9 !default;
$pink: #d6336c !default;
$red: #d63939 !default;
$orange: #f76707 !default;
$yellow: #f59f00 !default;
$lime: #74b816 !default;
$green: #2fb344 !default;
$teal: #0ca678 !default;
$cyan: #17a2b8 !default;
$blue: oklch(54.6% 0.1724 254.2deg) !default; // #066fd1
$azure: oklch(66.26% 0.1358 246.81deg) !default; // #4299e1
$indigo: oklch(55.66% 0.2082 268.35deg) !default; // #4263eb
$purple: oklch(57.83% 0.2186 319.15deg) !default; // #ae3ec9
$pink: oklch(58.81% 0.201 5.25deg) !default; // #d6336c
$red: oklch(58.29% 0.1941 25.59deg) !default; // #d63939
$orange: oklch(68.51% 0.1954 44.57deg) !default; // #f76707
$yellow: oklch(77.02% 0.1658 71.02deg) !default; // #f59f00
$lime: oklch(70.99% 0.1897 131.53deg) !default; // #74b816
$green: oklch(67.38% 0.1885 145.43deg) !default; // #2fb344
$teal: oklch(64.32% 0.1338 164.7deg) !default; // #0ca678
$cyan: oklch(65.52% 0.1105 212.17deg) !default; // #17a2b8
$colors: (
'blue': $blue,
@@ -629,17 +629,17 @@ $extra-colors: (
$social-colors: (
'x': #000,
'facebook': #1877f2,
'facebook': #0866ff,
'twitter': #1da1f2,
'linkedin': #0a66c2,
'google': #dc4e41,
'google': #4285f4,
'youtube': #f00,
'vimeo': #1ab7ea,
'dribbble': #ea4c89,
'github': #181717,
'instagram': #e4405f,
'instagram': #ff0069,
'pinterest': #bd081c,
'vk': #6383a8,
'vk': #07f,
'rss': #ffa500,
'flickr': #0063dc,
'bitbucket': #0052cc,
@@ -1335,8 +1335,8 @@ $carousel-control-hover-opacity: 0.9 !default;
$carousel-control-transition: opacity 0.15s ease !default;
$carousel-control-icon-filter: null !default;
$carousel-control-icon-width: 1.5rem !default;
$carousel-control-prev-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{$carousel-control-color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 18 9 12 15 6'></polyline></svg>") !default;
$carousel-control-next-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{$carousel-control-color}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='9 18 15 12 9 6'></polyline></svg>") !default;
$carousel-control-prev-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='15 18 9 12 15 6'></polyline></svg>") !default;
$carousel-control-next-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='9 18 15 12 9 6'></polyline></svg>") !default;
$carousel-indicator-width: 30px !default;
$carousel-indicator-height: 3px !default;
@@ -1593,11 +1593,9 @@ $navbar-light-active-color: var(--body-color) !default;
$navbar-light-hover-color: var(--body-color) !default;
$navbar-light-disabled-color: var(--disabled-color) !default;
$navbar-light-active-bg: color-transparent(var(--primary), 0.08) !default;
// Compile-time `rgba()` on purpose: this value is url-encoded into the svg data
// uri below, and an svg image cannot resolve `var()` or `color-mix()` with
// `var()` — the stroke would be dropped and the icon would disappear.
$navbar-light-icon-color: rgba($body-color, 0.75) !default;
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-icon-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
$navbar-light-icon-color: color-transparent(var(--body-color), 0.75) !default;
// Monochrome: painted with `--navbar-toggler-icon-color` through `mask-icon()`.
$navbar-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='black' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
$navbar-light-toggler-border-color: color-transparent(var(--emphasis-color), 0.15) !default;
$navbar-light-brand-hover-color: $navbar-light-active-color !default;
@@ -1607,9 +1605,7 @@ $navbar-dark-active-bg: color-transparent(var(--white), 0.06) !default;
$navbar-dark-brand-color: $white !default;
$navbar-dark-active-color: $white !default;
$navbar-dark-disabled-color: var(--disabled-color) !default;
// Compile-time `rgba()` on purpose, see `$navbar-light-icon-color`.
$navbar-dark-icon-color: rgba($white, $text-secondary-opacity) !default;
$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-dark-icon-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
$navbar-dark-icon-color: color-transparent(var(--white), $text-secondary-opacity) !default;
$navbar-dark-toggler-border-color: color-transparent(var(--white), 0.1) !default;
$navbar-dark-brand-hover-color: $navbar-dark-active-color !default;
@@ -1946,15 +1942,16 @@ $form-check-input-box-shadow: $input-box-shadow !default;
$form-check-input-checked-bg-size: 1.25rem !default;
$form-check-input-checked-bg-color: var(--primary) !default;
$form-check-input-checked-bg-repeat: repeat !default;
$form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'><path fill='none' stroke='#{$white}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/></svg>") !default;
$form-check-input-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'><path fill='none' stroke='#{$body-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/></svg>") !default;
$form-check-input-checked-color: $white !default;
// Monochrome icons, painted with `--form-check-icon-color` through `mask-icon()`.
$form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'><path fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/></svg>") !default;
$form-check-input-checked-border-color: $input-border-color-translucent !default;
$form-check-input-indeterminate-color: $component-active-color !default;
$form-check-input-indeterminate-bg-color: var(--primary) !default;
$form-check-input-indeterminate-border-color: $form-check-input-indeterminate-bg-color !default;
$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill='none' stroke='#{$form-check-input-indeterminate-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/></svg>") !default;
$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/></svg>") !default;
$form-check-radio-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle r='3' fill='#{$white}' cx='8' cy='8' /></svg>") !default;
$form-check-radio-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><circle r='3' fill='black' cx='8' cy='8' /></svg>") !default;
$form-check-input-disabled-opacity: 0.5 !default;
$form-check-label-disabled-opacity: $text-secondary-opacity;
@@ -1997,19 +1994,17 @@ $form-select-font-size-lg: $input-font-size-lg !default;
$form-select-border-radius-lg: $input-border-radius-lg !default;
$form-select-transition: $input-transition !default;
$form-switch-color: $gray-200 !default;
$form-switch-color: var(--gray-200) !default;
$form-switch-width: 2rem !default;
$form-switch-height: 1.25rem !default;
$form-switch-padding-start: $form-switch-width + 0.5rem !default;
$form-switch-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color}'/></svg>") !default;
$form-switch-border-radius: $form-switch-width !default;
$form-switch-transition: background-position 0.15s ease-in-out !default;
// Compile-time tint on purpose: url-encoded into the svg below, where `var()` cannot resolve.
$form-switch-focus-color: tint-color($component-active-bg, 50%) !default;
$form-switch-focus-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-focus-color}'/></svg>") !default;
$form-switch-checked-color: $white !default;
$form-switch-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-checked-color}'/></svg>") !default;
$form-switch-checked-bg-position: right center !default;
$form-switch-transition:
background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out !default;
$form-switch-knob-transition: inset-inline-start 0.15s ease-in-out !default;
$form-switch-focus-color: mix-color-css(var(--white), var(--primary), 50%) !default;
$form-switch-checked-color: var(--white) !default;
// Form range
$form-range-track-width: 100% !default;
+4 -5
View File
@@ -130,16 +130,15 @@
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background-repeat: no-repeat;
background-position: 50%;
background-size: 100% 100%;
background-color: currentColor;
@include mask-icon($size: 100% 100%, $position: 50%);
}
.carousel-control-prev-icon {
background-image: escape-svg($carousel-control-prev-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-next-icon-bg) + '*/'};
mask-image: escape-svg($carousel-control-prev-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-next-icon-bg) + '*/'};
}
.carousel-control-next-icon {
background-image: escape-svg($carousel-control-next-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-prev-icon-bg) + '*/'};
mask-image: escape-svg($carousel-control-next-icon-bg) #{'/*rtl:' + escape-svg($carousel-control-prev-icon-bg) + '*/'};
}
// Optional indicator pips/controls
+6 -7
View File
@@ -24,7 +24,8 @@
--navbar-toggler-padding-y: #{$navbar-toggler-padding-y};
--navbar-toggler-padding-x: #{$navbar-toggler-padding-x};
--navbar-toggler-font-size: #{$navbar-toggler-font-size};
--navbar-toggler-icon-bg: #{escape-svg($navbar-light-toggler-icon-bg)};
--navbar-toggler-icon-bg: #{escape-svg($navbar-toggler-icon-bg)};
--navbar-toggler-icon-color: #{$navbar-light-icon-color};
--navbar-toggler-border-color: #{$navbar-light-toggler-border-color};
--navbar-toggler-border-radius: #{$navbar-toggler-border-radius};
--navbar-toggler-focus-width: #{$navbar-toggler-focus-width};
@@ -175,10 +176,8 @@
width: 1.5em;
height: 1.5em;
vertical-align: middle;
background-image: var(--navbar-toggler-icon-bg);
background-repeat: no-repeat;
background-position: center;
background-size: 100%;
background-color: var(--navbar-toggler-icon-color);
@include mask-icon(var(--navbar-toggler-icon-bg), $size: 100%);
}
.navbar-nav-scroll {
@@ -278,14 +277,14 @@
--navbar-brand-color: #{$navbar-dark-brand-color};
--navbar-brand-hover-color: #{$navbar-dark-brand-hover-color};
--navbar-toggler-border-color: #{$navbar-dark-toggler-border-color};
--navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)};
--navbar-toggler-icon-color: #{$navbar-dark-icon-color};
// scss-docs-end navbar-dark-css-vars
}
@if $enable-dark-mode {
@include color-mode(dark) {
.navbar-toggler-icon {
--navbar-toggler-icon-bg: #{escape-svg($navbar-dark-toggler-icon-bg)};
--navbar-toggler-icon-color: #{$navbar-dark-icon-color};
}
}
}
+1 -1
View File
@@ -54,7 +54,7 @@ body {
text-align: var(--body-text-align);
background-color: var(--body-bg); // 2
-webkit-text-size-adjust: 100%; // 3
-webkit-tap-highlight-color: rgba($black, 0); // 4
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); // 4
}
// scss-docs-end reboot-body-rules
+50 -32
View File
@@ -30,7 +30,9 @@
.form-check-input {
--form-check-bg: #{$form-check-input-bg};
--form-check-icon-color: #{$form-check-input-checked-color};
position: relative;
flex-shrink: 0;
width: $form-check-input-width;
height: $form-check-input-width;
@@ -38,19 +40,32 @@
vertical-align: top;
appearance: none;
background-color: var(--form-check-bg);
background-image: var(--form-check-bg-image);
background-repeat: no-repeat;
background-position: center;
background-size: contain;
border: $form-check-input-border;
print-color-adjust: exact; // Keep themed appearance for print
@include transition($form-check-transition);
// The check, dash or dot is a pseudo-element painted through a CSS mask, so
// its colour comes from `--form-check-icon-color` instead of a colour baked
// into the svg. Hidden until the input is checked or indeterminate.
&::before {
position: absolute;
inset: 0;
pointer-events: none;
content: '';
background-color: var(--form-check-icon-color);
opacity: 0;
@include mask-icon(var(--form-check-icon));
}
&[type='checkbox'] {
--form-check-icon: #{escape-svg($form-check-input-checked-bg-image)};
@include border-radius($form-check-input-border-radius);
}
&[type='radio'] {
--form-check-icon: #{escape-svg($form-check-radio-checked-bg-image)};
// stylelint-disable-next-line property-disallowed-list
border-radius: $form-check-radio-border-radius;
}
@@ -69,31 +84,20 @@
background-color: $form-check-input-checked-bg-color;
border-color: $form-check-input-checked-border-color;
&[type='checkbox'] {
@if $enable-gradients {
--form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)}, var(--gradient);
} @else {
--form-check-bg-image: #{escape-svg($form-check-input-checked-bg-image)};
}
}
&[type='radio'] {
@if $enable-gradients {
--form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)}, var(--gradient);
} @else {
--form-check-bg-image: #{escape-svg($form-check-radio-checked-bg-image)};
}
&::before {
opacity: 1;
}
}
&[type='checkbox']:indeterminate {
--form-check-icon: #{escape-svg($form-check-input-indeterminate-bg-image)};
--form-check-icon-color: #{$form-check-input-indeterminate-color};
background-color: $form-check-input-indeterminate-bg-color;
border-color: $form-check-input-indeterminate-border-color;
@if $enable-gradients {
--form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)}, var(--gradient);
} @else {
--form-check-bg-image: #{escape-svg($form-check-input-indeterminate-bg-image)};
&::before {
opacity: 1;
}
}
@@ -127,26 +131,40 @@
padding-inline-start: $form-switch-padding-start;
.form-check-input {
--form-switch-bg: #{escape-svg($form-switch-bg-image)};
--form-switch-color: #{$form-switch-color};
// The knob is a circle three quarters of the track height, inset by an eighth
// — the same geometry the old `viewBox='-4 -4 8 8'` svg had.
--form-switch-knob-size: calc(#{$form-switch-height} * 0.75);
--form-switch-knob-offset: calc(#{$form-switch-height} * 0.125);
width: $form-switch-width;
margin-inline-start: $form-switch-padding-start * -1;
background-image: var(--form-switch-bg);
background-position: left center;
@include border-radius($form-switch-border-radius, 0);
@include transition($form-switch-transition);
// The knob replaces the masked check icon: a plain circle that slides.
&::before {
inset: 0 auto 0 var(--form-switch-knob-offset);
width: var(--form-switch-knob-size);
height: var(--form-switch-knob-size);
margin-block: auto;
background-color: var(--form-switch-color);
// stylelint-disable-next-line property-disallowed-list
border-radius: 50%;
opacity: 1;
mask-image: none;
@include transition($form-switch-knob-transition);
}
&:focus {
--form-switch-bg: #{escape-svg($form-switch-focus-bg-image)};
--form-switch-color: #{$form-switch-focus-color};
}
&:checked {
background-position: $form-switch-checked-bg-position;
--form-switch-color: #{$form-switch-checked-color};
@if $enable-gradients {
--form-switch-bg: #{escape-svg($form-switch-checked-bg-image)}, var(--gradient);
} @else {
--form-switch-bg: #{escape-svg($form-switch-checked-bg-image)};
&::before {
inset-inline-start: calc(100% - var(--form-switch-knob-size) - var(--form-switch-knob-offset));
}
}
}
@@ -185,7 +203,7 @@
@if $enable-dark-mode {
@include color-mode(dark) {
.form-switch .form-check-input:not(:checked):not(:focus) {
--form-switch-bg: #{escape-svg($form-switch-bg-image-dark)};
--form-switch-color: #{$form-switch-color-dark};
}
}
}
+1
View File
@@ -504,6 +504,7 @@ Navbar toggler
width: 1.25em;
height: 2px;
background: currentColor;
mask-image: none; // the bars below replace the masked hamburger from bootstrap/_navbar
@include border-radius(var(--border-radius-pill));
@include transition(top $navbar-toggler-animation-time $navbar-toggler-animation-time, bottom $navbar-toggler-animation-time $navbar-toggler-animation-time, transform $navbar-toggler-animation-time, opacity 0s $navbar-toggler-animation-time);
-11
View File
@@ -49,8 +49,6 @@
max-width: 65rem;
margin: 4rem auto;
@include border-radius($border-radius-lg);
//box-shadow: 0 10px 15px -3px rgba($color-text, 0.1),
// 0 4px 6px -2px rgba($color-text, 0.05);
img,
svg {
@@ -60,12 +58,3 @@
height: auto;
}
}
//
//.hero-img-side {
// img,
// svg {
// max-width: 100%;
// height: auto;
// display: block;
// }
//}
+7 -102
View File
@@ -35,38 +35,6 @@
@return oklch(from #{$color} calc(l #{$sign} #{math.abs($delta)}) c h);
}
// str-replace was defined here twice — the canonical copy lives in the
// "Bootstrap functions" section below.
@mixin media-breakpoint-down-than($name, $breakpoints: $grid-breakpoints) {
$prev: breakpoint-prev($name, $breakpoints);
@if $prev == null {
@content;
} @else {
$max: breakpoint-max($prev, $breakpoints);
@if $max {
@media (max-width: $max) {
@content;
}
} @else {
@content;
}
}
}
@function breakpoint-prev($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map.keys($breakpoints)) {
$n: list.index($breakpoint-names, $name);
@if not $n {
@error "breakpoint `#{$name}` not found in `#{$breakpoints}`";
}
@return if(sass($n > 1): list.nth($breakpoint-names, $n - 1); else: null);
}
// escape-svg was defined here twice — the canonical copy lives in the
// "Bootstrap functions" section below.
/**
* Converts a given value to a percentage string.
*
@@ -98,47 +66,18 @@
@return color-mix(in oklab, #{$color} #{to-percentage($alpha)}, $background);
}
@function url-svg($svg) {
$svg: str-replace($svg, '#', '%23');
$svg: str-replace($svg, '<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
@return url('data:image/svg+xml;charset=UTF-8,#{$svg}');
// Paints a monochrome svg data uri (authored with `black`) in a Sass colour, for
// the few places that cannot use `mask-icon()`: a pseudo-element that already
// carries a background, or a form control without pseudo-elements. Pass a Sass
// colour, never `var()` — an svg image cannot resolve custom properties.
@function svg-icon-color($svg, $color) {
@return str-replace($svg, "'black'", "'#{$color}'");
}
// Bootstrap functions
//
// Utility mixins and functions for evaluating source code across our variables, maps, and mixins.
// Ascending
// Used to evaluate Sass maps like our grid breakpoints.
@mixin _assert-ascending($map, $map-name) {
$prev-key: null;
$prev-num: null;
@each $key, $num in $map {
@if $prev-num == null or math.unit($num) == '%' or math.unit($prev-num) == '%' {
// Do nothing
} @else if not math.compatible($prev-num, $num) {
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
} @else if $prev-num >= $num {
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
}
$prev-key: $key;
$prev-num: $num;
}
}
// Starts at zero
// Used to ensure the min-width of the lowest breakpoint starts at 0.
@mixin _assert-starts-at-zero($map, $map-name: '$grid-breakpoints') {
@if list.length($map) > 0 {
$values: map.values($map);
$first-value: list.nth($values, 1);
@if $first-value != 0 {
@warn "First breakpoint in #{$map-name} must start at 0, but starts at #{$first-value}.";
}
}
}
// Colors
@function to-rgb($value) {
@return color.channel($value, 'red', $space: rgb), color.channel($value, 'green', $space: rgb), color.channel($value, 'blue', $space: rgb);
@@ -177,14 +116,6 @@
}
// stylelint-enable scss/dollar-variable-pattern
@function varify($list) {
$result: null;
@each $entry in $list {
$result: list.append($result, var(--#{$entry}), space);
}
@return $result;
}
// Internal Bootstrap function to turn maps into its negative variant.
// It prefixes the keys with `n` and makes the value negative.
@function negativify-map($map) {
@@ -197,32 +128,6 @@
@return $result;
}
// Get multiple keys from a sass map
@function map-get-multiple($map, $values) {
$result: ();
@each $key, $value in $map {
@if (list.index($values, $key) != null) {
$result: map.merge(
$result,
(
$key: $value,
)
);
}
}
@return $result;
}
// Merge multiple maps
@function map-merge-multiple($maps...) {
$merged-maps: ();
@each $map in $maps {
$merged-maps: map.merge($merged-maps, $map);
}
@return $merged-maps;
}
// Replace `$search` with `$replace` in `$string`
// Used on our SVG icon backgrounds for custom forms.
//
@@ -325,7 +230,7 @@ $_luminance-list: 0.0008 0.001 0.0011 0.0013 0.0015 0.0017 0.002 0.0022 0.0025 0
// Return opaque color
// opaque(#fff, rgba(0, 0, 0, .5)) => #808080
@function opaque($background, $foreground) {
@return color.mix(rgba($foreground, 1), $background, color.opacity($foreground) * 100%);
@return color.mix(color.change($foreground, $alpha: 1), $background, color.channel($foreground, 'alpha') * 100%, $method: srgb);
}
// Tint a color: mix a color with white (in oklab, see `mix-color()`)
+17
View File
@@ -79,6 +79,23 @@
//
// Print styles
//
// Renders an svg icon through a CSS mask so the shape is painted with the
// element's `background-color` and follows the theme without a colour baked
// into the data uri. Set `background-color` on the element; pass `null` for
// `$icon` when the mask image is applied per state.
@mixin mask-icon($icon: null, $size: contain, $position: center) {
@if $icon != null {
mask-image: $icon;
}
mask-repeat: no-repeat;
@if $position != null {
mask-position: $position;
}
@if $size != null {
mask-size: $size;
}
}
@mixin media-print {
@media print {
@content;
+55 -55
View File
@@ -2,17 +2,17 @@
[data-bs-theme-base='slate'],
[data-theme-base='slate'] {
--gray-50: #f8fafc;
--gray-100: #f1f5f9;
--gray-200: #e2e8f0;
--gray-300: #cbd5e1;
--gray-400: #94a3b8;
--gray-500: #64748b;
--gray-600: #475569;
--gray-700: #334155;
--gray-800: #1e293b;
--gray-900: #0f172a;
--gray-950: #020617;
--gray-50: oklch(98.42% 0.0034 247.86deg); // #f8fafc
--gray-100: oklch(96.83% 0.0069 247.9deg); // #f1f5f9
--gray-200: oklch(92.88% 0.0126 255.51deg); // #e2e8f0
--gray-300: oklch(86.9% 0.0198 252.89deg); // #cbd5e1
--gray-400: oklch(71.07% 0.0351 256.79deg); // #94a3b8
--gray-500: oklch(55.44% 0.0407 257.42deg); // #64748b
--gray-600: oklch(44.55% 0.0374 257.28deg); // #475569
--gray-700: oklch(37.17% 0.0392 257.29deg); // #334155
--gray-800: oklch(27.95% 0.0368 260.03deg); // #1e293b
--gray-900: oklch(20.77% 0.0398 265.75deg); // #0f172a
--gray-950: oklch(12.88% 0.0406 264.7deg); // #020617
}
[data-bs-theme-base='gray'],
@@ -32,63 +32,63 @@
[data-bs-theme-base='zinc'],
[data-theme-base='zinc'] {
--gray-50: #fafafa;
--gray-100: #f4f4f5;
--gray-200: #e4e4e7;
--gray-300: #d4d4d8;
--gray-400: #a1a1aa;
--gray-500: #71717a;
--gray-600: #52525b;
--gray-700: #3f3f46;
--gray-800: #27272a;
--gray-900: #18181b;
--gray-950: #09090b;
--gray-50: oklch(98.51% 0 0deg); // #fafafa
--gray-100: oklch(96.74% 0.0013 286.37deg); // #f4f4f5
--gray-200: oklch(91.97% 0.004 286.32deg); // #e4e4e7
--gray-300: oklch(87.11% 0.0055 286.29deg); // #d4d4d8
--gray-400: oklch(71.18% 0.0129 286.07deg); // #a1a1aa
--gray-500: oklch(55.17% 0.0138 285.94deg); // #71717a
--gray-600: oklch(44.19% 0.0146 285.79deg); // #52525b
--gray-700: oklch(37.03% 0.0119 285.81deg); // #3f3f46
--gray-800: oklch(27.39% 0.0055 286.03deg); // #27272a
--gray-900: oklch(21.03% 0.0059 285.89deg); // #18181b
--gray-950: oklch(14.08% 0.0044 285.82deg); // #09090b
}
[data-bs-theme-base='neutral'],
[data-theme-base='neutral'] {
--gray-50: #fafafa;
--gray-100: #f5f5f5;
--gray-200: #e5e5e5;
--gray-300: #d4d4d4;
--gray-400: #a3a3a3;
--gray-500: #737373;
--gray-600: #525252;
--gray-700: #404040;
--gray-800: #262626;
--gray-900: #171717;
--gray-950: #0a0a0a;
--gray-50: oklch(98.51% 0 0deg); // #fafafa
--gray-100: oklch(97.02% 0 0deg); // #f5f5f5
--gray-200: oklch(92.19% 0 0deg); // #e5e5e5
--gray-300: oklch(86.99% 0 0deg); // #d4d4d4
--gray-400: oklch(71.55% 0 0deg); // #a3a3a3
--gray-500: oklch(55.55% 0 0deg); // #737373
--gray-600: oklch(43.86% 0 0deg); // #525252
--gray-700: oklch(37.15% 0 0deg); // #404040
--gray-800: oklch(26.86% 0 0deg); // #262626
--gray-900: oklch(20.46% 0 0deg); // #171717
--gray-950: oklch(14.48% 0 0deg); // #0a0a0a
}
[data-bs-theme-base='stone'],
[data-theme-base='stone'] {
--gray-50: #fafaf9;
--gray-100: #f5f5f4;
--gray-200: #e7e5e4;
--gray-300: #d6d3d1;
--gray-400: #a8a29e;
--gray-500: #78716c;
--gray-600: #57534e;
--gray-700: #44403c;
--gray-800: #292524;
--gray-900: #1c1917;
--gray-950: #0c0a09;
--gray-50: oklch(98.48% 0.0013 106.42deg); // #fafaf9
--gray-100: oklch(96.99% 0.0013 106.42deg); // #f5f5f4
--gray-200: oklch(92.32% 0.0026 48.72deg); // #e7e5e4
--gray-300: oklch(86.87% 0.0043 56.37deg); // #d6d3d1
--gray-400: oklch(71.61% 0.0091 56.26deg); // #a8a29e
--gray-500: oklch(55.34% 0.0116 58.07deg); // #78716c
--gray-600: oklch(44.44% 0.0096 73.64deg); // #57534e
--gray-700: oklch(37.41% 0.0087 67.56deg); // #44403c
--gray-800: oklch(26.85% 0.0063 34.3deg); // #292524
--gray-900: oklch(21.61% 0.0061 56.04deg); // #1c1917
--gray-950: oklch(14.69% 0.0041 49.25deg); // #0c0a09
}
// Easter egg: not listed in the theme settings panel, enable with `data-bs-theme-base="pink"` or `?theme-base=pink`
[data-bs-theme-base='pink'],
[data-theme-base='pink'] {
--gray-50: #fdf2f8;
--gray-100: #fce7f3;
--gray-200: #fbcfe8;
--gray-300: #f9a8d4;
--gray-400: #f472b6;
--gray-500: #ec4899;
--gray-600: #db2777;
--gray-700: #be185d;
--gray-800: #9d174d;
--gray-900: #831843;
--gray-950: #500724;
--gray-50: oklch(97.14% 0.0141 343.2deg); // #fdf2f8
--gray-100: oklch(94.82% 0.0276 342.26deg); // #fce7f3
--gray-200: oklch(89.94% 0.0589 343.23deg); // #fbcfe8
--gray-300: oklch(82.28% 0.1095 346.02deg); // #f9a8d4
--gray-400: oklch(72.53% 0.1752 349.76deg); // #f472b6
--gray-500: oklch(65.59% 0.2118 354.31deg); // #ec4899
--gray-600: oklch(59.16% 0.218 0.58deg); // #db2777
--gray-700: oklch(52.46% 0.199 3.96deg); // #be185d
--gray-800: oklch(45.87% 0.1697 3.82deg); // #9d174d
--gray-900: oklch(40.78% 0.1442 2.43deg); // #831843
--gray-950: oklch(28.45% 0.1048 3.91deg); // #500724
}
@each $name, $value in $extra-colors {
+2 -8
View File
@@ -2,11 +2,7 @@
// "needs config" tier: they default to the global `$grid-breakpoints`, but every
// function also accepts an explicit `$breakpoints` map, so the tests pass a
// local fixture map and stay independent of the project variables.
//
// `breakpoint-prev` lives in mixins/_functions.scss; the rest live in
// mixins/bootstrap/_breakpoints.scss.
@use '../mixins/functions' as fn;
@use '../mixins/bootstrap/breakpoints' as *;
$grid: (
@@ -17,15 +13,13 @@ $grid: (
xl: 1200px,
);
@include describe('breakpoint-next / breakpoint-prev') {
@include describe('breakpoint-next') {
@include it('returns the neighbouring breakpoint name') {
@include assert-equal(breakpoint-next(md, $grid), lg);
@include assert-equal(fn.breakpoint-prev(md, $grid), sm);
}
@include it('returns null past the last / before the first breakpoint') {
@include it('returns null past the last breakpoint') {
@include assert-equal(breakpoint-next(xl, $grid), null);
@include assert-equal(fn.breakpoint-prev(xs, $grid), null);
}
}
-10
View File
@@ -67,16 +67,6 @@
}
}
@include describe('url-svg') {
@include it("percent-encodes '#' so the data URI stays valid") {
@include assert-equal(meta.inspect(url-svg('#')), 'url("data:image/svg+xml;charset=UTF-8,%23")');
}
@include it('injects the SVG xmlns attribute') {
@include assert-equal(meta.inspect(url-svg('<svg></svg>')), 'url(\'data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg"></svg>\')');
}
}
@include describe('map-loop') {
// Applies a module-level function (resolved by name via meta.get-function) to
// every value in the map. `to-percentage` lives in the same module, so it
+3 -24
View File
@@ -1,31 +1,10 @@
// Tests for the CSS custom-property builders in scss/mixins/_functions.scss:
// `varify()` and `rgba-css-var()`. A plain `@use` of the functions module is
// enough — no fixtures. Both emit unprefixed names: the `--tblr-` prefix is
// added later, by the css pipeline (.build/css-var-prefix.ts).
// Tests for `rgba-css-var()` in scss/mixins/_functions.scss. A plain `@use` of
// the functions module is enough — no fixtures. It emits unprefixed names: the
// `--tblr-` prefix is added later, by the css pipeline (.build/css-var-prefix.ts).
@use 'sass:meta';
@use '../mixins/functions' as *;
@include describe('varify') {
// `varify` seeds its accumulator with `null`, so the raw list inspects as
// `null var(...) ...`; Sass drops the null when serialising to CSS, so the
// behaviour is asserted through the rendered output rather than the value.
@include it('prefixes each entry as a space-separated var() list') {
@include assert() {
@include output() {
.t {
padding: varify(('gutter-x' 'gutter-y'));
}
}
@include expect() {
.t {
padding: var(--gutter-x) var(--gutter-y);
}
}
}
}
}
@include describe('rgba-css-var') {
@include it('builds a color-mix() from the identifier and target opacity') {
@include assert-equal(meta.inspect(rgba-css-var('primary', 'bg')), 'color-mix(in oklab, var(--primary) calc(var(--bg-opacity) * 100%), transparent)');
+5
View File
@@ -1,3 +1,4 @@
@use 'sass:meta';
// Tests for `escape-svg()` from scss/mixins/_functions.scss.
//
// It iterates the global `$escaped-characters` map, which lives in
@@ -8,6 +9,10 @@
@use '../mixins/functions' as *;
@include describe('escape-svg') {
@include it('percent-encodes % so oklch() lightness survives inside a data uri') {
@include assert-equal(meta.inspect(escape-svg('url("data:image/svg+xml,<path stroke=\'oklch(54.6% 0.17 254deg)\'/>")')), 'url("data:image/svg+xml,%3cpath stroke=\'oklch%2854.6%25 0.17 254deg%29\'/%3e")');
}
@include it('returns a non data-URI string unchanged') {
@include assert-equal(escape-svg('no data uri <here>'), 'no data uri <here>');
}
-41
View File
@@ -33,44 +33,3 @@
);
}
}
@include describe('map-get-multiple') {
@include it('keeps only the requested keys, preserving map order') {
@include assert-equal(
map-get-multiple(
(
a: 1,
b: 2,
c: 3,
),
(a c)
),
(
a: 1,
c: 3,
)
);
}
}
@include describe('map-merge-multiple') {
@include it('merges maps left-to-right, later keys winning') {
@include assert-equal(
map-merge-multiple(
(
a: 1,
),
(
b: 2,
),
(
a: 3,
)
),
(
a: 3,
b: 2,
)
);
}
}
@@ -2,10 +2,6 @@
// functions, every mixin accepts an explicit `$breakpoints` map, so the tests
// pass a local fixture map and stay independent of the global `$grid-breakpoints`.
//
// `media-breakpoint-{up,down,between,only}` come from
// mixins/bootstrap/_breakpoints.scss; `media-breakpoint-down-than` lives in
// mixins/_functions.scss. Their names don't collide, so both are used as `*`.
//
// Max-widths are the breakpoint value minus 0.02px (the min-/max- overlap fix);
// the smallest/edge cases emit the content with no media query at all.
@@ -137,66 +133,3 @@ $g: (
}
}
}
@include describe('media-breakpoint-down-than') {
@include it('targets everything below the previous breakpoint') {
@include assert() {
@include output() {
@include media-breakpoint-down-than(md, $g) {
.t {
color: red;
}
}
}
@include expect() {
@media (max-width: 575.98px) {
.t {
color: red;
}
}
}
}
}
@include it('emits content directly when there is no previous breakpoint') {
@include assert() {
@include output() {
@include media-breakpoint-down-than(xs, $g) {
.t {
color: red;
}
}
}
@include expect() {
.t {
color: red;
}
}
}
}
// Names that don't exist in the global $grid-breakpoints at all: this only
// resolves if $breakpoints actually reaches the breakpoint-prev() lookup,
// not just the breakpoint-max() one.
$custom: (
tablet: 0,
desktop: 900px,
);
@include it('resolves the previous breakpoint from a custom $breakpoints map') {
@include assert() {
@include output() {
@include media-breakpoint-down-than(desktop, $custom) {
.t {
color: red;
}
}
}
@include expect() {
.t {
color: red;
}
}
}
}
}
+2 -2
View File
@@ -35,7 +35,7 @@ Color Input
height: 100%;
content: '';
background: no-repeat center center/$form-check-input-checked-bg-size;
background-image: escape-svg($form-check-input-checked-bg-image);
background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $form-check-input-checked-color));
opacity: 0;
@include transition(opacity $transition-time);
@@ -51,7 +51,7 @@ Color Input
@at-root .form-colorinput-light & {
&::before {
background-image: escape-svg($form-check-input-checked-bg-image-dark);
background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $body-color));
}
}
}
+2 -2
View File
@@ -54,7 +54,7 @@ Image check
.form-imagecheck-input:checked ~ & {
background-color: $form-check-input-checked-bg-color;
background-image: escape-svg($form-check-input-checked-bg-image);
background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $form-check-input-checked-color));
background-repeat: $form-check-input-checked-bg-repeat;
background-position: center;
background-size: $form-check-input-checked-bg-size;
@@ -66,7 +66,7 @@ Image check
}
.form-imagecheck-input[type='radio']:checked ~ & {
background-image: escape-svg($form-check-radio-checked-bg-image);
background-image: escape-svg(svg-icon-color($form-check-radio-checked-bg-image, $form-check-input-checked-color));
}
}
}
+2 -2
View File
@@ -85,11 +85,11 @@ Select group
}
.form-selectgroup-input[type='checkbox']:checked + .form-selectgroup-label & {
background-image: escape-svg($form-check-input-checked-bg-image);
background-image: escape-svg(svg-icon-color($form-check-input-checked-bg-image, $form-check-input-checked-color));
}
.form-selectgroup-input[type='radio']:checked + .form-selectgroup-label & {
background-image: escape-svg($form-check-radio-checked-bg-image);
background-image: escape-svg(svg-icon-color($form-check-radio-checked-bg-image, $form-check-input-checked-color));
}
}
+2 -10
View File
@@ -103,13 +103,7 @@ Coloris({
})
```
To read the colors from the theme instead of writing them by hand, take them from the CSS variables:
```js
const swatches = ['--tblr-blue', '--tblr-azure', '--tblr-indigo'].map(function (prop) {
return getComputedStyle(document.body).getPropertyValue(prop)
})
```
Write the swatches as hex values. Tabler's CSS variables such as `--tblr-blue` hold `oklch()` colors, which Coloris cannot parse, so do not read the swatches from them.
### Swatches only
@@ -192,9 +186,7 @@ Use the picker like any other form field: a label, the input, and a hint below i
window.addEventListener('load', function () {
if (typeof Coloris === 'undefined') return;
const swatches = ['--tblr-blue', '--tblr-azure', '--tblr-indigo', '--tblr-purple', '--tblr-pink', '--tblr-red', '--tblr-orange', '--tblr-green'].map(function (prop) {
return getComputedStyle(document.body).getPropertyValue(prop).trim();
});
const swatches = ['#066fd1', '#4299e1', '#4263eb', '#ae3ec9', '#d6336c', '#d63939', '#f76707', '#2fb344'];
Coloris({ el: '.form-control[id^="colorpicker"]', selectInput: false, format: 'hex', alpha: false, swatchesOnly: false, swatches: swatches });
+5 -5
View File
@@ -15,8 +15,10 @@ interface Props {
const { value, id, format = false, class: className, ariaLabel } = Astro.props
const alpha = false
const colors = site.colors as Record<string, { prop: string }>
const swatchProps = Object.values(colors).map((color) => color.prop)
// Coloris parses hex, rgb and hsl only, and the CSS tokens are `oklch()`, so the
// swatches use the hex values from site.json.
const colors = site.colors as Record<string, { hex: string }>
const swatches = Object.values(colors).map((color) => color.hex)
const colorpickerId = `colorpicker-${id}`
const colorpickerSelector = `#colorpicker-${id}`
@@ -25,12 +27,10 @@ const colorpickerSelector = `#colorpicker-${id}`
<input type="text" class={`form-control d-block${className ? ` ${className}` : ''}`} id={colorpickerId} value={value} aria-label={ariaLabel ?? 'Color value'} />
<CaptureScript>
<!-- BEGIN COLORPICKER -->
<script is:inline define:vars={{ colorpickerId, colorpickerSelector, alpha, format, swatchProps }}>
<script is:inline define:vars={{ colorpickerId, colorpickerSelector, alpha, format, swatches }}>
function initColorpicker() {
window.tabler_colorpicker ??= {}
const swatches = swatchProps.map((prop) => window.getComputedStyle(document.body).getPropertyValue(prop))
if (window.Coloris) {
// `el` can only be configured globally, but Coloris.setInstance() scopes the rest
// (alpha, format, swatches, ...) to this selector so multiple pickers with