Sabil UI

Search documentation

Search for a command to run...

Getting Started

HTML, CSS & JS

The same foundation as one CSS file and one small script. No React, no Tailwind, no build step. Works in plain HTML, Laravel, Django, Rails, Vue, WordPress — anything that renders HTML.

Setup#

Download sabil.css and sabil.js, then link both with the Inter font. Add s-body to <body>. Every class starts with s- and every token with --s-, so nothing clashes with your other CSS. The script has no dependencies and is only needed for the interactive components.

<!doctype html><html lang="en">  <head>    <meta charset="utf-8">    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&display=swap">    <link rel="stylesheet" href="sabil.css">    <script src="sabil.js" defer></script>  </head>  <body class="s-body">    <button class="s-btn" data-s-toast="Hello from Sabil">Sign in</button>  </body></html>

Dark mode#

Add class="dark" or data-theme="dark" to <html>. The previews below follow the theme of this site.

<html lang="en" class="dark">

JavaScript API#

Components wire themselves up from data-s-* attributes, including markup added after the page loads. When you need code, use the global Sabil object and listen for its events.

// ToastsSabil.toast("Saved")Sabil.toast({ title: "Upload failed", description: "Max 10 MB.", variant: "destructive" })Sabil.toast({ title: "Archived", action: { label: "Undo", onClick: () => restore() } })// Dialogs and sheetsSabil.openDialog("edit-profile")Sabil.closeDialog("edit-profile")// After inserting tabs or comboboxes with JavaScriptSabil.init(container)// Events (they bubble)menu.addEventListener("s-select", (e) => console.log(e.detail.value))combobox.addEventListener("s-change", (e) => console.log(e.detail.value))tabs.addEventListener("s-change", (e) => console.log(e.detail.tab))

Components#

These need only sabil.css. Each preview runs in an isolated frame, so what you see is exactly what a plain HTML page gets.

Button

Primary uses the blue gradient surface. Add a modifier for other variants and sizes.

Input & Field

Wrap a label, control and hint in s-field. Set aria-invalid for the error state.

Textarea & Select

Native elements with the same border, radius and focus ring as the input.

Checkbox, Radio & Switch

Native inputs restyled. Keyboard and form support come for free.

Segmented Control

Tabs without JavaScript, built on radio inputs. Use a shared name per group.

Badge

Small status labels.

Card

Header, content and footer. Any element works inside.

Alert

A callout with an optional icon.

Avatar

Initials or an image. Group them with s-avatar-group.

Accordion

Native <details>. Opens and closes without JavaScript, and stays accessible.

Table

Readable defaults for tabular data.

Breadcrumb & Pagination

Mark the current page with aria-current.

Progress, Skeleton & Spinner

Loading states. The skeleton shimmer and spinner stop when reduced motion is on.

Tooltip & Kbd

A CSS-only tooltip from the data-s-tooltip attribute. It waits 300ms before showing.

Empty State

A placeholder for lists with nothing in them yet.

Interactive#

These also need sabil.js. Keyboard support, focus management and enter/exit motion are built in. Dialogs and sheets use the native <dialog> element.

Tabs

Tabs with panels. Arrow keys, Home and End move between tabs.

Dialog

Built on native <dialog>: focus is trapped, Escape and the backdrop close it, and focus returns to the trigger.

Alert Dialog

The same dialog, used to confirm a destructive action.

Sheet

A dialog that slides in from the edge with the drawer curve. Add s-sheet--left for the other side.

Dropdown Menu

Opens under its trigger and grows from it. Arrow keys move, Escape closes, focus returns to the button.

Popover

Rich content in a floating panel. Click outside or press Escape to close.

Combobox

Type to filter, arrows to move, Enter to pick. Listen for the s-change event to read the value.

Toast

Call Sabil.toast() from any script, or put data-s-toast on a button. Hover pauses the timer.

Full source#

sabil.css
/*! * Sabil UI — HTML, CSS & JS edition * No Tailwind, no build step. Link this file and use the s-* classes. * Interactive components (tabs, dialog, sheet, menu, popover, combobox, * toast) also need sabil.js. * *   <link rel="stylesheet" href="sabil.css"> *   <body class="s-body"> ... </body> * * Dark mode: add class="dark" or data-theme="dark" on <html>. *//* ------------------------------------------------------------------ * Tokens * ------------------------------------------------------------------ */:root {  --s-font: "Inter", "Inter Variable", system-ui, -apple-system, "Segoe UI", sans-serif;  --s-font-mono: ui-monospace, "SF Mono", "Geist Mono", Menlo, Consolas, monospace;  --s-radius-sm: 4px;  --s-radius-md: 6px;  --s-radius-lg: 8px;  --s-radius-xl: 12px;  --s-radius-2xl: 16px;  --s-ease-out: cubic-bezier(0.23, 1, 0.32, 1);  --s-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);  --s-ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);  --s-bg: #ffffff;  --s-fg: #0a0a0a;  --s-card: #ffffff;  --s-popover: #ffffff;  --s-primary: #056dff;  --s-primary-highlight: #3c86ff;  --s-primary-fg: #ffffff;  --s-secondary: #f1f5f9;  --s-secondary-fg: #0f172b;  --s-muted: #f1f5f9;  --s-muted-fg: #45556c;  --s-accent: #0a0a0a0a;  --s-destructive: #e7000b;  --s-border: #0a0a0a14;  --s-input: #0a0a0a1f;  --s-ring: #056dff;  --s-raised: #ffffff;  --s-sunken: #f1f5f9;  --s-panel: #f9f8f6;  --s-surface-edge: #00000040;  --s-shadow: #0000000d;}.dark,[data-theme="dark"] {  --s-bg: #0a0a0a;  --s-fg: #fafafa;  --s-card: #121212;  --s-popover: #161616;  --s-primary: #2b7bff;  --s-primary-highlight: #5c9bff;  --s-secondary: #1d1d1f;  --s-secondary-fg: #fafafa;  --s-muted: #1d1d1f;  --s-muted-fg: #a1a1a6;  --s-accent: #ffffff0f;  --s-destructive: #ff6467;  --s-border: #ffffff17;  --s-input: #ffffff24;  --s-ring: #3c86ff;  --s-raised: #1d1d1f;  --s-sunken: #1d1d1f;  --s-panel: #1a1918;  --s-surface-edge: #00000080;  --s-shadow: #00000066;}/* ------------------------------------------------------------------ * Base * ------------------------------------------------------------------ */.s-body {  margin: 0;  background: var(--s-bg);  color: var(--s-fg);  font-family: var(--s-font);  font-size: 14px;  line-height: 1.5;  letter-spacing: -0.006em;  -webkit-font-smoothing: antialiased;}[class^="s-"],[class*=" s-"],[class^="s-"]::before,[class*=" s-"]::before,[class^="s-"]::after,[class*=" s-"]::after {  box-sizing: border-box;}.s-focus:focus-visible,.s-btn:focus-visible,.s-pagination__link:focus-visible,.s-accordion summary:focus-visible {  outline: 2px solid var(--s-ring);  outline-offset: 2px;}/* ------------------------------------------------------------------ * Button * ------------------------------------------------------------------ */.s-btn {  display: inline-flex;  align-items: center;  justify-content: center;  gap: 8px;  height: 40px;  margin: 0;  padding: 0 16px 2px;  border: 0;  border-radius: var(--s-radius-lg);  font: inherit;  font-size: 14px;  font-weight: 500;  line-height: 1;  letter-spacing: -0.01em;  white-space: nowrap;  text-decoration: none;  cursor: pointer;  user-select: none;  color: var(--s-primary-fg);  background-color: var(--s-primary);  background-image: linear-gradient(180deg, var(--s-primary-highlight) 0%, var(--s-primary) 100%);  box-shadow:    inset 0 0 1px var(--s-surface-edge),    inset 0 1px 0 #ffffff1f,    0 1px 2px var(--s-shadow);  transition:    transform 150ms var(--s-ease-out),    filter 150ms ease,    background-color 150ms ease,    color 150ms ease;}.s-btn svg {  width: 16px;  height: 16px;  flex-shrink: 0;}.s-btn:active {  transform: scale(0.97);}.s-btn:disabled,.s-btn[aria-disabled="true"] {  opacity: 0.5;  pointer-events: none;}@media (hover: hover) {  .s-btn:hover {    filter: brightness(1.06);  }}.s-btn--outline {  color: var(--s-fg);  background: var(--s-raised);  box-shadow:    0 0 0 1px var(--s-border),    0 1px 2px var(--s-shadow);}.s-btn--secondary {  color: var(--s-secondary-fg);  background: var(--s-secondary);  box-shadow: none;}.s-btn--ghost {  color: var(--s-fg);  background: transparent;  box-shadow: none;}.s-btn--destructive {  color: var(--s-destructive);  background: color-mix(in oklab, var(--s-destructive) 10%, transparent);  box-shadow: none;}.s-btn--link {  height: auto;  padding: 0;  color: var(--s-primary);  background: transparent;  box-shadow: none;  text-underline-offset: 4px;}@media (hover: hover) {  .s-btn--outline:hover,  .s-btn--ghost:hover {    filter: none;    background: color-mix(in oklab, var(--s-bg), var(--s-fg) 4%);  }  .s-btn--secondary:hover {    filter: none;    background: color-mix(in oklab, var(--s-secondary), var(--s-fg) 5%);  }  .s-btn--destructive:hover {    filter: none;    background: color-mix(in oklab, var(--s-destructive) 18%, transparent);  }  .s-btn--link:hover {    filter: none;    text-decoration: underline;  }}.s-btn--sm {  height: 32px;  padding: 0 12px;  gap: 6px;  font-size: 13px;  border-radius: var(--s-radius-md);}.s-btn--lg {  height: 44px;  padding: 0 20px 2px;}.s-btn--icon {  width: 40px;  padding: 0;}.s-btn--icon.s-btn--sm {  width: 32px;}.s-btn--block {  width: 100%;}/* ------------------------------------------------------------------ * Form controls * ------------------------------------------------------------------ */.s-input,.s-textarea,.s-select {  display: block;  width: 100%;  height: 40px;  margin: 0;  padding: 0 12px;  border: 1px solid var(--s-input);  border-radius: var(--s-radius-lg);  background: var(--s-bg);  color: var(--s-fg);  font: inherit;  font-size: 14px;  box-shadow: 0 1px 2px var(--s-shadow);  outline: none;  transition:    border-color 150ms ease,    box-shadow 150ms ease;}.s-input::placeholder,.s-textarea::placeholder {  color: var(--s-muted-fg);}.s-input:focus-visible,.s-textarea:focus-visible,.s-select:focus-visible {  border-color: var(--s-ring);  box-shadow: 0 0 0 3px color-mix(in oklab, var(--s-ring) 25%, transparent);}.s-input[aria-invalid="true"],.s-textarea[aria-invalid="true"],.s-select[aria-invalid="true"] {  border-color: var(--s-destructive);}.s-input:disabled,.s-textarea:disabled,.s-select:disabled {  opacity: 0.5;  cursor: not-allowed;}.s-textarea {  height: auto;  min-height: 80px;  padding: 8px 12px;  resize: vertical;}.s-select {  appearance: none;  padding-right: 36px;  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");  background-repeat: no-repeat;  background-position: right 12px center;  background-size: 16px;}.s-label {  display: inline-block;  font-size: 14px;  font-weight: 500;  line-height: 1.2;}.s-field {  display: grid;  gap: 8px;}.s-field__hint {  margin: 0;  font-size: 13px;  color: var(--s-muted-fg);}.s-field__error {  margin: 0;  font-size: 13px;  color: var(--s-destructive);}/* Checkbox, radio, switch: native inputs with custom looks. */.s-choice {  display: inline-flex;  align-items: center;  gap: 8px;  font-size: 14px;  cursor: pointer;}.s-checkbox,.s-radio {  appearance: none;  flex-shrink: 0;  width: 16px;  height: 16px;  margin: 0;  border: 1px solid var(--s-input);  background: var(--s-bg);  box-shadow: 0 1px 2px var(--s-shadow);  cursor: pointer;  transition:    background-color 150ms ease,    border-color 150ms ease;}.s-checkbox {  border-radius: var(--s-radius-sm);}.s-radio {  border-radius: 50%;}.s-checkbox:checked {  border-color: var(--s-primary);  background: var(--s-primary)    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E")    center / 12px no-repeat;}.s-radio:checked {  border-color: var(--s-primary);  background: radial-gradient(circle, var(--s-primary) 0 3.5px, var(--s-bg) 4px);}.s-checkbox:focus-visible,.s-radio:focus-visible,.s-switch:focus-visible {  outline: 2px solid var(--s-ring);  outline-offset: 2px;}.s-switch {  appearance: none;  position: relative;  flex-shrink: 0;  width: 32px;  height: 18px;  margin: 0;  border-radius: 999px;  background: color-mix(in oklab, var(--s-muted-fg) 35%, transparent);  cursor: pointer;  transition: background-color 150ms ease;}.s-switch::before {  content: "";  position: absolute;  top: 2px;  left: 2px;  width: 14px;  height: 14px;  border-radius: 50%;  background: #ffffff;  box-shadow: 0 1px 2px #0000002e;  transition: transform 200ms var(--s-ease-out);}.s-switch:checked {  background: var(--s-primary);}.s-switch:checked::before {  transform: translateX(14px);}/* ------------------------------------------------------------------ * Segmented control (tabs without JS, radio inputs) * ------------------------------------------------------------------ */.s-tabs {  display: flex;  gap: 2px;  padding: 2px;  border-radius: var(--s-radius-lg);  background: var(--s-sunken);}.s-tabs__item {  flex: 1;  position: relative;}.s-tabs__item input {  position: absolute;  opacity: 0;  pointer-events: none;}.s-tabs__item span {  display: flex;  align-items: center;  justify-content: center;  height: 32px;  padding: 0 12px;  border-radius: var(--s-radius-md);  font-size: 14px;  font-weight: 500;  color: var(--s-muted-fg);  cursor: pointer;  user-select: none;  transition:    color 150ms ease,    background-color 150ms ease,    box-shadow 150ms ease;}.s-tabs__item input:checked + span {  background: var(--s-bg);  color: var(--s-secondary-fg);  box-shadow: 0 1px 2px var(--s-shadow);}.dark .s-tabs__item input:checked + span,[data-theme="dark"] .s-tabs__item input:checked + span {  background: #ffffff1a;  color: var(--s-fg);}.s-tabs__item input:focus-visible + span {  outline: 2px solid var(--s-ring);  outline-offset: 1px;}/* ------------------------------------------------------------------ * Badge, kbd, avatar, separator * ------------------------------------------------------------------ */.s-badge {  display: inline-flex;  align-items: center;  gap: 4px;  height: 20px;  padding: 0 8px;  border: 1px solid transparent;  border-radius: 999px;  font-size: 12px;  font-weight: 500;  white-space: nowrap;  color: var(--s-primary-fg);  background: var(--s-primary);}.s-badge--secondary {  color: var(--s-secondary-fg);  background: var(--s-secondary);}.s-badge--outline {  color: var(--s-fg);  background: transparent;  border-color: var(--s-border);}.s-badge--destructive {  color: var(--s-destructive);  background: color-mix(in oklab, var(--s-destructive) 12%, transparent);}.s-kbd {  display: inline-flex;  align-items: center;  justify-content: center;  min-width: 20px;  height: 20px;  padding: 0 4px;  border-radius: var(--s-radius-sm);  background: var(--s-muted);  color: var(--s-muted-fg);  font: 500 12px/1 var(--s-font);  box-shadow: inset 0 -1px 0 var(--s-border);}.s-avatar {  display: inline-flex;  align-items: center;  justify-content: center;  flex-shrink: 0;  width: 32px;  height: 32px;  overflow: hidden;  border-radius: 50%;  background: var(--s-muted);  color: var(--s-fg);  font-size: 12px;  font-weight: 500;}.s-avatar img {  width: 100%;  height: 100%;  object-fit: cover;}.s-avatar--lg {  width: 40px;  height: 40px;  font-size: 14px;}.s-avatar-group {  display: inline-flex;}.s-avatar-group > .s-avatar {  box-shadow: 0 0 0 2px var(--s-bg);}.s-avatar-group > .s-avatar + .s-avatar {  margin-left: -8px;}.s-separator {  height: 1px;  margin: 0;  border: 0;  background: var(--s-border);}.s-separator--vertical {  width: 1px;  height: auto;  align-self: stretch;}/* ------------------------------------------------------------------ * Card, alert, empty state * ------------------------------------------------------------------ */.s-card {  display: flex;  flex-direction: column;  gap: 20px;  padding: 24px;  border: 1px solid var(--s-border);  border-radius: var(--s-radius-xl);  background: var(--s-card);  color: var(--s-fg);  box-shadow: 0 1px 2px var(--s-shadow);}.s-card__header {  display: grid;  gap: 4px;}.s-card__title {  margin: 0;  font-size: 16px;  font-weight: 600;  letter-spacing: -0.01em;}.s-card__description {  margin: 0;  font-size: 14px;  color: var(--s-muted-fg);}.s-card__footer {  display: flex;  gap: 8px;}.s-alert {  display: grid;  grid-template-columns: 16px 1fr;  gap: 2px 12px;  padding: 12px 16px;  border: 1px solid var(--s-border);  border-radius: var(--s-radius-xl);  background: var(--s-card);  font-size: 14px;}.s-alert > svg {  grid-row: span 2;  width: 16px;  height: 16px;  margin-top: 2px;}.s-alert__title {  margin: 0;  font-weight: 500;}.s-alert__description {  margin: 0;  color: var(--s-muted-fg);}.s-alert--destructive,.s-alert--destructive .s-alert__description {  color: var(--s-destructive);}.s-empty {  display: flex;  flex-direction: column;  align-items: center;  gap: 8px;  padding: 40px 24px;  border: 1px dashed var(--s-border);  border-radius: var(--s-radius-xl);  text-align: center;}.s-empty__icon {  display: flex;  align-items: center;  justify-content: center;  width: 40px;  height: 40px;  margin-bottom: 4px;  border-radius: var(--s-radius-lg);  background: var(--s-muted);}.s-empty__icon svg {  width: 20px;  height: 20px;}.s-empty__title {  margin: 0;  font-size: 15px;  font-weight: 600;}.s-empty__description {  margin: 0 0 8px;  max-width: 320px;  color: var(--s-muted-fg);}/* ------------------------------------------------------------------ * Accordion (native <details>) * ------------------------------------------------------------------ */.s-accordion {  border-bottom: 1px solid var(--s-border);}.s-accordion summary {  display: flex;  align-items: center;  justify-content: space-between;  gap: 16px;  padding: 16px 0;  font-size: 14px;  font-weight: 500;  list-style: none;  cursor: pointer;}.s-accordion summary::-webkit-details-marker {  display: none;}.s-accordion summary::after {  content: "";  width: 16px;  height: 16px;  flex-shrink: 0;  background: currentColor;  opacity: 0.6;  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;  transition: transform 200ms var(--s-ease-out);}.s-accordion[open] summary::after {  transform: rotate(180deg);}.s-accordion__content {  padding-bottom: 16px;  color: var(--s-muted-fg);}/* ------------------------------------------------------------------ * Table, breadcrumb, pagination * ------------------------------------------------------------------ */.s-table {  width: 100%;  border-collapse: collapse;  font-size: 14px;}.s-table th {  height: 40px;  padding: 0 8px;  border-bottom: 1px solid var(--s-border);  color: var(--s-muted-fg);  font-weight: 500;  text-align: left;}.s-table td {  padding: 10px 8px;  border-bottom: 1px solid var(--s-border);}.s-table tr:last-child td {  border-bottom: 0;}@media (hover: hover) {  .s-table tbody tr:hover td {    background: var(--s-accent);  }}.s-breadcrumb {  display: flex;  flex-wrap: wrap;  align-items: center;  gap: 6px;  margin: 0;  padding: 0;  list-style: none;  font-size: 14px;  color: var(--s-muted-fg);}.s-breadcrumb li + li::before {  content: "/";  margin-right: 6px;  opacity: 0.6;}.s-breadcrumb a {  color: inherit;  text-decoration: none;  transition: color 150ms ease;}.s-breadcrumb a:hover,.s-breadcrumb [aria-current="page"] {  color: var(--s-fg);}.s-pagination {  display: flex;  align-items: center;  gap: 4px;}.s-pagination__link {  display: inline-flex;  align-items: center;  justify-content: center;  min-width: 36px;  height: 36px;  padding: 0 10px;  border-radius: var(--s-radius-lg);  color: var(--s-fg);  font-size: 14px;  font-weight: 500;  text-decoration: none;  transition: background-color 150ms ease;}@media (hover: hover) {  .s-pagination__link:hover {    background: var(--s-accent);  }}.s-pagination__link[aria-current="page"] {  background: var(--s-raised);  box-shadow:    0 0 0 1px var(--s-border),    0 1px 2px var(--s-shadow);}/* ------------------------------------------------------------------ * Feedback: progress, skeleton, spinner, tooltip * ------------------------------------------------------------------ */.s-progress {  appearance: none;  display: block;  width: 100%;  height: 8px;  border: 0;  border-radius: 999px;  overflow: hidden;  background: var(--s-muted);}.s-progress::-webkit-progress-bar {  background: var(--s-muted);}.s-progress::-webkit-progress-value {  border-radius: 999px;  background: var(--s-primary);  transition: width 300ms var(--s-ease-out);}.s-progress::-moz-progress-bar {  border-radius: 999px;  background: var(--s-primary);}.s-skeleton {  display: block;  border-radius: var(--s-radius-md);  background: linear-gradient(      90deg,      var(--s-muted) 0%,      color-mix(in oklab, var(--s-muted), var(--s-bg) 70%) 50%,      var(--s-muted) 100%    )    0 0 / 200% 100%;  animation: s-shimmer 1.4s linear infinite;}@keyframes s-shimmer {  from {    background-position: 100% 0;  }  to {    background-position: -100% 0;  }}.s-spinner {  display: inline-block;  width: 16px;  height: 16px;  border: 2px solid currentColor;  border-top-color: transparent;  border-radius: 50%;  animation: s-spin 0.6s linear infinite;}@keyframes s-spin {  to {    transform: rotate(360deg);  }}/* Usage: <button data-s-tooltip="Copy">…</button> */[data-s-tooltip] {  position: relative;}[data-s-tooltip]::after {  content: attr(data-s-tooltip);  position: absolute;  bottom: calc(100% + 8px);  left: 50%;  z-index: 50;  padding: 6px 10px;  border-radius: var(--s-radius-md);  background: var(--s-fg);  color: var(--s-bg);  font: 500 12px/1.2 var(--s-font);  white-space: nowrap;  pointer-events: none;  opacity: 0;  transform: translate(-50%, 4px) scale(0.97);  transform-origin: bottom center;  transition:    opacity 150ms var(--s-ease-out),    transform 150ms var(--s-ease-out);}[data-s-tooltip]:hover::after,[data-s-tooltip]:focus-visible::after {  opacity: 1;  transform: translate(-50%, 0) scale(1);  transition-delay: 300ms;}/* ================================================================== * Interactive components — need sabil.js * Open/close state is data-state="open" | "closed", set by the script. * ================================================================== *//* ------------------------------------------------------------------ * Tabs with panels * ------------------------------------------------------------------ */.s-tablist {  display: flex;  gap: 2px;  padding: 2px;  border-radius: var(--s-radius-lg);  background: var(--s-sunken);}.s-tab {  flex: 1;  height: 32px;  padding: 0 12px;  border: 0;  border-radius: var(--s-radius-md);  background: transparent;  color: var(--s-muted-fg);  font: inherit;  font-size: 14px;  font-weight: 500;  cursor: pointer;  transition:    color 150ms ease,    background-color 150ms ease,    box-shadow 150ms ease;}.s-tab:hover {  color: var(--s-fg);}.s-tab[aria-selected="true"] {  background: var(--s-bg);  color: var(--s-secondary-fg);  box-shadow: 0 1px 2px var(--s-shadow);}.dark .s-tab[aria-selected="true"],[data-theme="dark"] .s-tab[aria-selected="true"] {  background: #ffffff1a;  color: var(--s-fg);}.s-tab:focus-visible {  outline: 2px solid var(--s-ring);  outline-offset: 1px;}.s-tabpanel {  padding-top: 12px;  font-size: 14px;}.s-tabpanel:focus-visible {  outline: 2px solid var(--s-ring);  outline-offset: 4px;  border-radius: var(--s-radius-md);}/* ------------------------------------------------------------------ * Dialog & sheet (native <dialog>) * ------------------------------------------------------------------ */.s-dialog,.s-sheet {  padding: 0;  border: 0;  background: transparent;  color: var(--s-fg);  overflow: visible;}.s-dialog {  width: 420px;  max-width: calc(100% - 32px);}.s-dialog::backdrop,.s-sheet::backdrop {  background: rgb(0 0 0 / 0.3);  opacity: 0;  transition: opacity 200ms var(--s-ease-out);}.s-dialog[data-state="open"]::backdrop,.s-sheet[data-state="open"]::backdrop {  opacity: 1;}.s-dialog__panel {  display: grid;  gap: 16px;  padding: 24px;  border-radius: var(--s-radius-xl);  background: var(--s-popover);  box-shadow:    0 0 0 1px var(--s-border),    0 24px 48px -12px rgb(0 0 0 / 0.18);  opacity: 0;  transform: scale(0.96);  transition:    opacity 150ms var(--s-ease-out),    transform 150ms var(--s-ease-out);}.s-dialog[data-state="open"] .s-dialog__panel {  opacity: 1;  transform: none;  transition-duration: 200ms;}.s-dialog__header {  display: grid;  gap: 6px;}.s-dialog__title {  margin: 0;  font-size: 16px;  font-weight: 600;  letter-spacing: -0.01em;}.s-dialog__description {  margin: 0;  font-size: 14px;  color: var(--s-muted-fg);}.s-dialog__footer {  display: flex;  flex-wrap: wrap;  justify-content: flex-end;  gap: 8px;}.s-dialog__close {  position: absolute;  top: 12px;  right: 12px;}.s-sheet {  width: 380px;  max-width: calc(100% - 48px);  height: 100%;  max-height: 100%;  margin: 0 0 0 auto;}.s-sheet__panel {  display: flex;  flex-direction: column;  gap: 16px;  height: 100%;  padding: 24px;  background: var(--s-popover);  box-shadow:    -1px 0 0 var(--s-border),    -24px 0 48px -12px rgb(0 0 0 / 0.18);  transform: translateX(100%);  transition: transform 250ms var(--s-ease-drawer);}.s-sheet[data-state="open"] .s-sheet__panel {  transform: none;  transition-duration: 400ms;}.s-sheet--left {  margin: 0 auto 0 0;}.s-sheet--left .s-sheet__panel {  box-shadow:    1px 0 0 var(--s-border),    24px 0 48px -12px rgb(0 0 0 / 0.18);  transform: translateX(-100%);}/* ------------------------------------------------------------------ * Menu & popover (positioned by sabil.js) * ------------------------------------------------------------------ */.s-menu,.s-popover {  position: fixed;  z-index: 50;  margin: 0;  border-radius: var(--s-radius-xl);  background: var(--s-popover);  color: var(--s-fg);  box-shadow:    0 0 0 1px var(--s-border),    0 10px 24px -6px rgb(0 0 0 / 0.12);  opacity: 0;  transform: scale(0.96);  transition:    opacity 120ms var(--s-ease-out),    transform 120ms var(--s-ease-out);}.s-menu[data-state="open"],.s-popover[data-state="open"] {  opacity: 1;  transform: none;  transition-duration: 180ms;}.s-menu[hidden],.s-popover[hidden] {  display: none;}.s-menu {  min-width: 200px;  padding: 4px;}.s-popover {  width: 288px;  padding: 16px;}.s-menu__item {  display: flex;  align-items: center;  gap: 8px;  width: 100%;  height: 32px;  padding: 0 8px;  border: 0;  border-radius: var(--s-radius-lg);  background: transparent;  color: inherit;  font: inherit;  font-size: 14px;  text-align: left;  text-decoration: none;  cursor: pointer;  outline: none;}.s-menu__item svg {  width: 16px;  height: 16px;  color: var(--s-muted-fg);}.s-menu__item:hover,.s-menu__item:focus-visible,.s-menu__item:focus {  background: var(--s-accent);}.s-menu__item--destructive,.s-menu__item--destructive svg {  color: var(--s-destructive);}.s-menu__label {  padding: 6px 8px;  font-size: 12px;  font-weight: 500;  color: var(--s-muted-fg);}.s-menu__separator {  height: 1px;  margin: 4px -4px;  background: var(--s-border);}.s-menu__shortcut {  margin-left: auto;  font-size: 12px;  color: var(--s-muted-fg);}/* ------------------------------------------------------------------ * Combobox * ------------------------------------------------------------------ */.s-combobox {  position: relative;  width: 100%;}.s-listbox {  position: absolute;  top: calc(100% + 6px);  left: 0;  right: 0;  z-index: 50;  max-height: 240px;  margin: 0;  padding: 4px;  overflow-y: auto;  list-style: none;  border-radius: var(--s-radius-xl);  background: var(--s-popover);  box-shadow:    0 0 0 1px var(--s-border),    0 10px 24px -6px rgb(0 0 0 / 0.12);  transform-origin: top center;  opacity: 0;  transform: scale(0.97);  transition:    opacity 120ms var(--s-ease-out),    transform 120ms var(--s-ease-out);}.s-listbox[data-state="open"] {  opacity: 1;  transform: none;  transition-duration: 180ms;}.s-listbox[hidden] {  display: none;}.s-option {  display: flex;  align-items: center;  justify-content: space-between;  height: 32px;  padding: 0 8px;  border-radius: var(--s-radius-lg);  font-size: 14px;  cursor: pointer;}.s-option[data-active="true"] {  background: var(--s-accent);}.s-option[aria-selected="true"]::after {  content: "";  width: 16px;  height: 16px;  background: var(--s-primary);  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;}.s-option[hidden] {  display: none;}.s-listbox__empty {  padding: 16px 8px;  font-size: 14px;  text-align: center;  color: var(--s-muted-fg);}/* ------------------------------------------------------------------ * Toast * ------------------------------------------------------------------ */.s-toaster {  position: fixed;  right: 16px;  bottom: 16px;  z-index: 100;  display: flex;  flex-direction: column-reverse;  gap: 8px;  width: 356px;  max-width: calc(100% - 32px);  pointer-events: none;}.s-toast {  display: grid;  grid-template-columns: 1fr auto;  align-items: center;  gap: 2px 12px;  padding: 14px 16px;  border-radius: var(--s-radius-xl);  background: var(--s-popover);  color: var(--s-fg);  font-size: 14px;  box-shadow:    0 0 0 1px var(--s-border),    0 10px 24px -6px rgb(0 0 0 / 0.12);  pointer-events: auto;  opacity: 0;  transform: translateY(100%);  transition:    opacity 400ms var(--s-ease-out),    transform 400ms var(--s-ease-out);}.s-toast[data-state="open"] {  opacity: 1;  transform: none;}.s-toast[data-state="closed"] {  opacity: 0;  transform: scale(0.96);  transition-duration: 150ms;}.s-toast__title {  margin: 0;  font-weight: 500;}.s-toast__description {  grid-column: 1;  margin: 0;  color: var(--s-muted-fg);}.s-toast > .s-btn {  grid-row: 1 / span 2;  grid-column: 2;}.s-toast--destructive .s-toast__title {  color: var(--s-destructive);}/* ------------------------------------------------------------------ * Reduced motion * ------------------------------------------------------------------ */@media (prefers-reduced-motion: reduce) {  [class^="s-"],  [class*=" s-"],  [class^="s-"]::before,  [class*=" s-"]::before,  [class^="s-"]::after,  [class*=" s-"]::after {    animation-duration: 1ms !important;    transition-duration: 1ms !important;  }}
sabil.js
/*! * Sabil UI — sabil.js * Vanilla JavaScript for the interactive components in sabil.css. * No dependencies. Load once, anywhere on the page: * *   <script src="sabil.js" defer></script> * * Components wire themselves up from data attributes, including markup * added later (all listeners are delegated to document). * *   Tabs       <div data-s-tabs> role="tablist" / role="tab" / role="tabpanel" *   Dialog     <button data-s-open="id">  <dialog id="id" class="s-dialog"> *   Sheet      <button data-s-open="id">  <dialog id="id" class="s-sheet"> *   Close      any element with data-s-close inside a dialog or sheet *   Menu       <button data-s-menu="id">  <div id="id" class="s-menu" role="menu" hidden> *   Popover    <button data-s-popover="id">  <div id="id" class="s-popover" hidden> *   Combobox   <div class="s-combobox" data-s-combobox> input + role="listbox" *   Toast      Sabil.toast({ title, description, variant, action }) *              or <button data-s-toast="Title" data-s-toast-description="…"> */;(function () {  "use strict"  var EXIT_MS = 160  var Sabil = (window.Sabil = window.Sabil || {})  function $(sel, root) {    return (root || document).querySelector(sel)  }  function $$(sel, root) {    return Array.prototype.slice.call((root || document).querySelectorAll(sel))  }  /* Opens an element: unhide, then set data-state on the next frame so   * the CSS transition runs from the closed state. */  function show(el) {    clearTimeout(el._kTimer)    el.hidden = false    el.setAttribute("data-state", "closed")    requestAnimationFrame(function () {      requestAnimationFrame(function () {        el.setAttribute("data-state", "open")      })    })  }  /* Closes an element: play the exit transition, then hide it. */  function hide(el, done) {    el.setAttribute("data-state", "closed")    clearTimeout(el._kTimer)    el._kTimer = setTimeout(function () {      if (el.getAttribute("data-state") === "closed") {        el.hidden = true        if (done) done()      }    }, EXIT_MS)  }  /* ----------------------------------------------------------------   * Tabs   * ---------------------------------------------------------------- */  function selectTab(tab, focus) {    var list = tab.closest('[role="tablist"]')    var root = tab.closest("[data-s-tabs]") || document    $$('[role="tab"]', list).forEach(function (t) {      var on = t === tab      t.setAttribute("aria-selected", on ? "true" : "false")      t.tabIndex = on ? 0 : -1      var panel = document.getElementById(t.getAttribute("aria-controls"))      if (panel) panel.hidden = !on    })    if (focus) tab.focus()    root.dispatchEvent(new CustomEvent("s-change", { detail: { tab: tab }, bubbles: true }))  }  function initTabs(root) {    $$('[data-s-tabs] [role="tablist"]', root).forEach(function (list) {      var tabs = $$('[role="tab"]', list)      var selected = tabs.filter(function (t) {        return t.getAttribute("aria-selected") === "true"      })[0] || tabs[0]      if (selected) selectTab(selected, false)    })  }  document.addEventListener("click", function (e) {    var tab = e.target.closest('[data-s-tabs] [role="tab"]')    if (tab) selectTab(tab, false)  })  document.addEventListener("keydown", function (e) {    var tab = e.target.closest && e.target.closest('[data-s-tabs] [role="tab"]')    if (!tab) return    var tabs = $$('[role="tab"]', tab.closest('[role="tablist"]'))    var i = tabs.indexOf(tab)    var next = null    if (e.key === "ArrowRight") next = tabs[(i + 1) % tabs.length]    if (e.key === "ArrowLeft") next = tabs[(i - 1 + tabs.length) % tabs.length]    if (e.key === "Home") next = tabs[0]    if (e.key === "End") next = tabs[tabs.length - 1]    if (next) {      e.preventDefault()      selectTab(next, true)    }  })  /* ----------------------------------------------------------------   * Dialog & sheet (native <dialog>: focus trap, Escape, top layer)   * ---------------------------------------------------------------- */  function openDialog(dialog, trigger) {    if (!dialog || dialog.open) return    dialog._kTrigger = trigger || document.activeElement    dialog.showModal()    dialog.setAttribute("data-state", "closed")    requestAnimationFrame(function () {      requestAnimationFrame(function () {        dialog.setAttribute("data-state", "open")      })    })  }  function closeDialog(dialog) {    if (!dialog || !dialog.open) return    var isSheet = dialog.classList.contains("s-sheet")    dialog.setAttribute("data-state", "closed")    setTimeout(function () {      dialog.close()      if (dialog._kTrigger && dialog._kTrigger.focus) dialog._kTrigger.focus()    }, isSheet ? 250 : EXIT_MS)  }  Sabil.openDialog = function (id) {    openDialog(document.getElementById(id))  }  Sabil.closeDialog = function (id) {    closeDialog(document.getElementById(id))  }  document.addEventListener("click", function (e) {    var opener = e.target.closest("[data-s-open]")    if (opener) {      openDialog(document.getElementById(opener.getAttribute("data-s-open")), opener)      return    }    var closer = e.target.closest("[data-s-close]")    if (closer) {      closeDialog(closer.closest("dialog"))      return    }    // A click on the <dialog> element itself is a click on the backdrop,    // because the visible panel is a child that fills the content box.    if (e.target.matches && e.target.matches("dialog.s-dialog, dialog.s-sheet")) {      closeDialog(e.target)    }  })  // Escape: animate out instead of closing instantly.  document.addEventListener(    "cancel",    function (e) {      if (e.target.matches && e.target.matches("dialog.s-dialog, dialog.s-sheet")) {        e.preventDefault()        closeDialog(e.target)      }    },    true  )  /* ----------------------------------------------------------------   * Menu & popover (shared floating logic)   * ---------------------------------------------------------------- */  var openFloating = null // { el, trigger, kind }  /* Places el under (or above) the trigger and sets the transform   * origin to the trigger side, so it grows out of the button. */  function position(trigger, el) {    var r = trigger.getBoundingClientRect()    var w = el.offsetWidth    var h = el.offsetHeight    var gap = 6    var vw = document.documentElement.clientWidth    var vh = document.documentElement.clientHeight    var align = el.getAttribute("data-align") || "start"    var top = r.bottom + gap    var originY = "top"    if (top + h > vh - 8 && r.top - gap - h > 8) {      top = r.top - gap - h      originY = "bottom"    }    var left = align === "end" ? r.right - w : align === "center" ? r.left + r.width / 2 - w / 2 : r.left    left = Math.max(8, Math.min(left, vw - w - 8))    var originX = Math.max(0, Math.min(w, r.left + r.width / 2 - left))    el.style.top = top + "px"    el.style.left = left + "px"    el.style.transformOrigin = originX + "px " + originY  }  function openFloat(el, trigger, kind) {    if (openFloating) closeFloat(false)    el.hidden = false    position(trigger, el)    show(el)    trigger.setAttribute("aria-expanded", "true")    openFloating = { el: el, trigger: trigger, kind: kind }    if (kind === "menu") {      var first = menuItems(el)[0]      if (first) first.focus()    } else {      var focusable = $("input, textarea, select, button, [href], [tabindex]:not([tabindex='-1'])", el)      if (focusable) focusable.focus()    }  }  function closeFloat(returnFocus) {    if (!openFloating) return    var o = openFloating    openFloating = null    o.trigger.setAttribute("aria-expanded", "false")    hide(o.el)    if (returnFocus) o.trigger.focus()  }  function menuItems(menu) {    return $$('[role="menuitem"]:not([disabled]), [role="menuitemcheckbox"]:not([disabled])', menu)  }  function toggle(trigger, attr, kind) {    var el = document.getElementById(trigger.getAttribute(attr))    if (!el) return    if (openFloating && openFloating.el === el) closeFloat(true)    else openFloat(el, trigger, kind)  }  document.addEventListener("click", function (e) {    var menuTrigger = e.target.closest("[data-s-menu]")    if (menuTrigger) return toggle(menuTrigger, "data-s-menu", "menu")    var popTrigger = e.target.closest("[data-s-popover]")    if (popTrigger) return toggle(popTrigger, "data-s-popover", "popover")    var item = e.target.closest('.s-menu [role="menuitem"]')    if (item && openFloating) {      openFloating.el.dispatchEvent(        new CustomEvent("s-select", { detail: { item: item, value: item.getAttribute("data-value") || item.textContent.trim() }, bubbles: true })      )      closeFloat(true)      return    }    if (openFloating && !openFloating.el.contains(e.target)) closeFloat(false)  })  document.addEventListener("keydown", function (e) {    // Arrow keys on a closed menu trigger open it.    var trigger = e.target.closest && e.target.closest("[data-s-menu]")    if (trigger && (e.key === "ArrowDown" || e.key === "ArrowUp") && !openFloating) {      e.preventDefault()      return toggle(trigger, "data-s-menu", "menu")    }    if (!openFloating) return    if (e.key === "Escape") {      e.preventDefault()      return closeFloat(true)    }    if (openFloating.kind !== "menu") return    var items = menuItems(openFloating.el)    var i = items.indexOf(document.activeElement)    var next = null    if (e.key === "ArrowDown") next = items[(i + 1) % items.length]    if (e.key === "ArrowUp") next = items[(i - 1 + items.length) % items.length]    if (e.key === "Home") next = items[0]    if (e.key === "End") next = items[items.length - 1]    if (e.key === "Tab") return closeFloat(false)    if (next) {      e.preventDefault()      next.focus()    }  })  window.addEventListener("resize", function () {    if (openFloating) position(openFloating.trigger, openFloating.el)  })  window.addEventListener(    "scroll",    function () {      if (openFloating) position(openFloating.trigger, openFloating.el)    },    true  )  /* ----------------------------------------------------------------   * Combobox   * ---------------------------------------------------------------- */  function comboParts(root) {    return {      input: $('input[role="combobox"]', root),      list: $('[role="listbox"]', root),      empty: $(".s-listbox__empty", root),    }  }  function visibleOptions(list) {    return $$('[role="option"]', list).filter(function (o) {      return !o.hidden    })  }  function setActive(root, option) {    var p = comboParts(root)    $$('[role="option"]', p.list).forEach(function (o) {      o.removeAttribute("data-active")    })    if (option) {      option.setAttribute("data-active", "true")      p.input.setAttribute("aria-activedescendant", option.id)      option.scrollIntoView({ block: "nearest" })    } else {      p.input.removeAttribute("aria-activedescendant")    }  }  function filterCombo(root) {    var p = comboParts(root)    var q = p.input.value.trim().toLowerCase()    var any = false    $$('[role="option"]', p.list).forEach(function (o) {      var match = o.textContent.toLowerCase().indexOf(q) !== -1      o.hidden = !match      if (match) any = true    })    if (p.empty) p.empty.hidden = any    setActive(root, visibleOptions(p.list)[0])  }  function openCombo(root) {    var p = comboParts(root)    if (!p.list.hidden && p.list.getAttribute("data-state") === "open") return    show(p.list)    p.input.setAttribute("aria-expanded", "true")  }  function closeCombo(root) {    var p = comboParts(root)    if (p.list.hidden) return    p.input.setAttribute("aria-expanded", "false")    hide(p.list)  }  function chooseOption(root, option) {    var p = comboParts(root)    $$('[role="option"]', p.list).forEach(function (o) {      o.setAttribute("aria-selected", o === option ? "true" : "false")    })    p.input.value = option.textContent.trim()    closeCombo(root)    root.dispatchEvent(      new CustomEvent("s-change", { detail: { value: option.getAttribute("data-value") || p.input.value }, bubbles: true })    )  }  function initCombobox(root) {    $$("[data-s-combobox]", root).forEach(function (combo, n) {      var p = comboParts(combo)      if (!p.input || !p.list) return      if (!p.list.id) p.list.id = "s-listbox-" + n      p.input.setAttribute("aria-controls", p.list.id)      p.input.setAttribute("aria-expanded", "false")      p.input.setAttribute("aria-autocomplete", "list")      $$('[role="option"]', p.list).forEach(function (o, i) {        if (!o.id) o.id = p.list.id + "-" + i        o.classList.add("s-option")      })    })  }  document.addEventListener("input", function (e) {    var root = e.target.closest && e.target.closest("[data-s-combobox]")    if (!root) return    openCombo(root)    filterCombo(root)  })  document.addEventListener("focusin", function (e) {    var root = e.target.closest && e.target.closest("[data-s-combobox]")    if (root && e.target.matches('input[role="combobox"]')) {      openCombo(root)      filterCombo(root)    }  })  document.addEventListener("mousedown", function (e) {    var option = e.target.closest('[data-s-combobox] [role="option"]')    if (option) {      e.preventDefault() // keep focus in the input      chooseOption(option.closest("[data-s-combobox]"), option)      return    }    $$("[data-s-combobox]").forEach(function (root) {      if (!root.contains(e.target)) closeCombo(root)    })  })  document.addEventListener("keydown", function (e) {    var root = e.target.closest && e.target.closest("[data-s-combobox]")    if (!root) return    var p = comboParts(root)    var options = visibleOptions(p.list)    var current = options.indexOf($('[data-active="true"]', p.list))    if (e.key === "ArrowDown" || e.key === "ArrowUp") {      e.preventDefault()      openCombo(root)      var step = e.key === "ArrowDown" ? 1 : -1      setActive(root, options[(current + step + options.length) % options.length])    } else if (e.key === "Enter") {      if (!p.list.hidden && options[current]) {        e.preventDefault()        chooseOption(root, options[current])      }    } else if (e.key === "Escape") {      closeCombo(root)    } else if (e.key === "Tab") {      closeCombo(root)    }  })  /* ----------------------------------------------------------------   * Toast   * ---------------------------------------------------------------- */  var MAX_TOASTS = 3  function toaster() {    var el = $(".s-toaster")    if (!el) {      el = document.createElement("ol")      el.className = "s-toaster"      el.setAttribute("aria-live", "polite")      document.body.appendChild(el)    }    return el  }  function dismiss(toast) {    if (toast._kGone) return    toast._kGone = true    clearTimeout(toast._kTimer)    toast.setAttribute("data-state", "closed")    setTimeout(function () {      toast.remove()    }, EXIT_MS)  }  Sabil.toast = function (options) {    var o = typeof options === "string" ? { title: options } : options || {}    var list = toaster()    var toast = document.createElement("li")    toast.className = "s-toast" + (o.variant === "destructive" ? " s-toast--destructive" : "")    toast.setAttribute("role", o.variant === "destructive" ? "alert" : "status")    var title = document.createElement("p")    title.className = "s-toast__title"    title.textContent = o.title || ""    toast.appendChild(title)    if (o.description) {      var desc = document.createElement("p")      desc.className = "s-toast__description"      desc.textContent = o.description      toast.appendChild(desc)    }    if (o.action) {      var btn = document.createElement("button")      btn.type = "button"      btn.className = "s-btn s-btn--outline s-btn--sm"      btn.textContent = o.action.label      btn.addEventListener("click", function () {        if (o.action.onClick) o.action.onClick()        dismiss(toast)      })      toast.appendChild(btn)    }    list.appendChild(toast)    requestAnimationFrame(function () {      requestAnimationFrame(function () {        toast.setAttribute("data-state", "open")      })    })    // Auto-dismiss, paused while hovered or while the tab is hidden.    var duration = o.duration || 4000    var remaining = duration    var started = Date.now()    function start() {      started = Date.now()      toast._kTimer = setTimeout(function () {        dismiss(toast)      }, remaining)    }    function pause() {      clearTimeout(toast._kTimer)      remaining -= Date.now() - started    }    toast.addEventListener("mouseenter", pause)    toast.addEventListener("mouseleave", start)    document.addEventListener("visibilitychange", function () {      if (toast._kGone) return      if (document.hidden) pause()      else start()    })    start()    var live = $$(".s-toast:not([data-state='closed'])", list)    if (live.length > MAX_TOASTS) dismiss(live[0])    return { dismiss: function () { dismiss(toast) } }  }  document.addEventListener("click", function (e) {    var t = e.target.closest("[data-s-toast]")    if (t) {      Sabil.toast({        title: t.getAttribute("data-s-toast"),        description: t.getAttribute("data-s-toast-description") || undefined,        variant: t.getAttribute("data-s-toast-variant") || undefined,      })    }  })  /* ----------------------------------------------------------------   * Init   * ---------------------------------------------------------------- */  Sabil.init = function (root) {    initTabs(root)    initCombobox(root)  }  if (document.readyState === "loading") {    document.addEventListener("DOMContentLoaded", function () {      Sabil.init()    })  } else {    Sabil.init()  }})()