diff --git a/docs/src/_includes/macros/code_block.njk b/docs/src/_includes/macros/code_block.njk index 5495c6c..f60a19a 100644 --- a/docs/src/_includes/macros/code_block.njk +++ b/docs/src/_includes/macros/code_block.njk @@ -16,7 +16,7 @@ " class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground group" > - {% lucide "clipboard", {"class": "group-[.copied]:hidden" } %} + {% lucide "copy", {"class": "group-[.copied]:hidden" } %} {% lucide "check", {"class": "hidden group-[.copied]:block" } %} diff --git a/docs/src/_includes/partials/sidebar.njk b/docs/src/_includes/partials/sidebar.njk index 6b2e736..98bc8d4 100644 --- a/docs/src/_includes/partials/sidebar.njk +++ b/docs/src/_includes/partials/sidebar.njk @@ -111,11 +111,13 @@ { label: "Label", url: "/components/label", attrs: link_attrs }, { label: "Pagination", url: "/components/pagination", attrs: link_attrs }, { label: "Popover", url: "/components/popover", attrs: link_attrs }, + { label: "Progress", url: "/components/progress", attrs: new_link_attrs }, { label: "Radio Group", url: "/components/radio-group", attrs: link_attrs }, { label: "Select", url: "/components/select", attrs: link_attrs }, { label: "Sidebar", url: "/components/sidebar", attrs: link_attrs }, { label: "Skeleton", url: "/components/skeleton", attrs: link_attrs }, { label: "Slider", url: "/components/slider", attrs: link_attrs }, + { label: "Spinner", url: "/components/spinner", attrs: new_link_attrs }, { label: "Switch", url: "/components/switch", attrs: link_attrs }, { label: "Table", url: "/components/table", attrs: link_attrs }, { label: "Tabs", url: "/components/tabs", attrs: link_attrs }, diff --git a/docs/src/assets/styles.css b/docs/src/assets/styles.css index a68b102..25433bf 100644 --- a/docs/src/assets/styles.css +++ b/docs/src/assets/styles.css @@ -7,6 +7,7 @@ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --color-red-500: oklch(63.7% 0.237 25.331); --color-amber-50: oklch(98.7% 0.022 95.277); --color-amber-100: oklch(96.2% 0.059 95.617); --color-amber-900: oklch(41.4% 0.112 45.904); @@ -25,6 +26,7 @@ --color-blue-800: oklch(42.4% 0.199 265.638); --color-blue-900: oklch(37.9% 0.146 265.522); --color-blue-950: oklch(28.2% 0.091 267.935); + --color-purple-500: oklch(62.7% 0.265 303.9); --color-neutral-300: oklch(87% 0 0); --color-neutral-800: oklch(26.9% 0 0); --color-black: #000; @@ -257,9 +259,6 @@ .invisible { visibility: hidden; } - .visible { - visibility: visible; - } .sr-only { position: absolute; width: 1px; @@ -301,15 +300,9 @@ .top-22 { top: calc(var(--spacing) * 22); } - .right-1 { - right: calc(var(--spacing) * 1); - } .right-1\.5 { right: calc(var(--spacing) * 1.5); } - .right-2 { - right: calc(var(--spacing) * 2); - } .right-2\.5 { right: calc(var(--spacing) * 2.5); } @@ -319,12 +312,6 @@ .bottom-0 { bottom: calc(var(--spacing) * 0); } - .bottom-2 { - bottom: calc(var(--spacing) * 2); - } - .bottom-3 { - bottom: calc(var(--spacing) * 3); - } .left-1\.5 { left: calc(var(--spacing) * 1.5); } @@ -439,6 +426,9 @@ .hidden { display: none; } + .inline { + display: inline; + } .inline-block { display: inline-block; } @@ -498,6 +488,12 @@ width: calc(var(--spacing) * 12); height: calc(var(--spacing) * 12); } + .h-1\.5 { + height: calc(var(--spacing) * 1.5); + } + .h-2 { + height: calc(var(--spacing) * 2); + } .h-4 { height: calc(var(--spacing) * 4); } @@ -516,8 +512,8 @@ .h-14 { height: calc(var(--spacing) * 14); } - .h-auto { - height: auto; + .h-full { + height: 100%; } .max-h-64 { max-height: calc(var(--spacing) * 64); @@ -534,33 +530,12 @@ .max-h-\[650px\] { max-height: 650px; } - .min-h-21 { - min-height: calc(var(--spacing) * 21); - } - .min-h-22 { - min-height: calc(var(--spacing) * 22); - } - .min-h-24 { - min-height: calc(var(--spacing) * 24); - } .min-h-25 { min-height: calc(var(--spacing) * 25); } - .min-h-26 { - min-height: calc(var(--spacing) * 26); - } .min-h-27 { min-height: calc(var(--spacing) * 27); } - .min-h-28 { - min-height: calc(var(--spacing) * 28); - } - .min-h-30 { - min-height: calc(var(--spacing) * 30); - } - .min-h-32 { - min-height: calc(var(--spacing) * 32); - } .min-h-77 { min-height: calc(var(--spacing) * 77); } @@ -582,6 +557,15 @@ .w-4 { width: calc(var(--spacing) * 4); } + .w-7 { + width: calc(var(--spacing) * 7); + } + .w-7\.5 { + width: calc(var(--spacing) * 7.5); + } + .w-8 { + width: calc(var(--spacing) * 8); + } .w-10 { width: calc(var(--spacing) * 10); } @@ -618,12 +602,6 @@ .w-max { width: max-content; } - .max-w-23 { - max-width: calc(var(--spacing) * 23); - } - .max-w-24 { - max-width: calc(var(--spacing) * 24); - } .max-w-72 { max-width: calc(var(--spacing) * 72); } @@ -657,6 +635,9 @@ .max-w-xl { max-width: var(--container-xl); } + .max-w-xs { + max-width: var(--container-xs); + } .min-w-0 { min-width: calc(var(--spacing) * 0); } @@ -855,14 +836,14 @@ .self-start { align-self: flex-start; } - .self-stretch { - align-self: stretch; - } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .overflow-hidden { + overflow: hidden; + } .overflow-x-auto { overflow-x: auto; } @@ -875,9 +856,6 @@ .rounded-\[2px\] { border-radius: 2px; } - .rounded-\[calc\(var\(--radius\)-5px\)\] { - border-radius: calc(var(--radius) - 5px); - } .rounded-full { border-radius: calc(infinity * 1px); } @@ -948,18 +926,9 @@ border-color: color-mix(in oklab, var(--color-border) 50%, transparent); } } - .border-input { - border-color: var(--color-input); - } .border-muted { border-color: var(--color-muted); } - .border-muted-foreground\/20 { - border-color: var(--color-muted-foreground); - @supports (color: color-mix(in lab, red, red)) { - border-color: color-mix(in oklab, var(--color-muted-foreground) 20%, transparent); - } - } .border-transparent { border-color: transparent; } @@ -999,6 +968,15 @@ .bg-primary { background-color: var(--color-primary); } + .bg-primary\/20 { + background-color: var(--color-primary); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-primary) 20%, transparent); + } + } + .bg-secondary { + background-color: var(--color-secondary); + } .bg-sidebar-primary { background-color: var(--color-sidebar-primary); } @@ -1026,6 +1004,9 @@ .p-4 { padding: calc(var(--spacing) * 4); } + .p-5 { + padding: calc(var(--spacing) * 5); + } .p-6 { padding: calc(var(--spacing) * 6); } @@ -1068,9 +1049,6 @@ .py-4 { padding-block: calc(var(--spacing) * 4); } - .py-15 { - padding-block: calc(var(--spacing) * 15); - } .py-\[0\.2rem\] { padding-block: 0.2rem; } @@ -1080,18 +1058,6 @@ .pt-2 { padding-top: calc(var(--spacing) * 2); } - .pt-3 { - padding-top: calc(var(--spacing) * 3); - } - .pt-10 { - padding-top: calc(var(--spacing) * 10); - } - .pt-12 { - padding-top: calc(var(--spacing) * 12); - } - .pt-14 { - padding-top: calc(var(--spacing) * 14); - } .pt-15 { padding-top: calc(var(--spacing) * 15); } @@ -1113,48 +1079,24 @@ .pr-20 { padding-right: calc(var(--spacing) * 20); } - .pr-22 { - padding-right: calc(var(--spacing) * 22); - } .pr-24 { padding-right: calc(var(--spacing) * 24); } .pr-26 { padding-right: calc(var(--spacing) * 26); } - .pr-28 { - padding-right: calc(var(--spacing) * 28); - } .pr-30 { padding-right: calc(var(--spacing) * 30); } - .pr-32 { - padding-right: calc(var(--spacing) * 32); - } - .pr-129 { - padding-right: calc(var(--spacing) * 129); - } .pb-3 { padding-bottom: calc(var(--spacing) * 3); } .pb-4 { padding-bottom: calc(var(--spacing) * 4); } - .pb-10 { - padding-bottom: calc(var(--spacing) * 10); - } - .pb-10\.5 { - padding-bottom: calc(var(--spacing) * 10.5); - } - .pb-11 { - padding-bottom: calc(var(--spacing) * 11); - } .pb-12 { padding-bottom: calc(var(--spacing) * 12); } - .pb-14 { - padding-bottom: calc(var(--spacing) * 14); - } .pb-17 { padding-bottom: calc(var(--spacing) * 17); } @@ -1173,27 +1115,9 @@ .pl-9 { padding-left: calc(var(--spacing) * 9); } - .pl-10 { - padding-left: calc(var(--spacing) * 10); - } - .pl-12 { - padding-left: calc(var(--spacing) * 12); - } - .pl-14 { - padding-left: calc(var(--spacing) * 14); - } .pl-15 { padding-left: calc(var(--spacing) * 15); } - .pl-16 { - padding-left: calc(var(--spacing) * 16); - } - .pl-19 { - padding-left: calc(var(--spacing) * 19); - } - .pl-20 { - padding-left: calc(var(--spacing) * 20); - } .pl-21 { padding-left: calc(var(--spacing) * 21); } @@ -1295,6 +1219,9 @@ .text-amber-900 { color: var(--color-amber-900); } + .text-blue-500 { + color: var(--color-blue-500); + } .text-destructive { color: var(--color-destructive); } @@ -1313,9 +1240,18 @@ .text-primary-foreground { color: var(--color-primary-foreground); } + .text-purple-500 { + color: var(--color-purple-500); + } + .text-red-500 { + color: var(--color-red-500); + } .text-sidebar-primary-foreground { color: var(--color-sidebar-primary-foreground); } + .text-yellow-500 { + color: var(--color-yellow-500); + } .uppercase { text-transform: uppercase; } @@ -1357,10 +1293,6 @@ --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .ring { - --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - } .ring-offset-background { --tw-ring-offset-color: var(--color-background); } @@ -1436,19 +1368,14 @@ visibility: visible; } } - .group-data-\[disabled\=true\]\:pointer-events-none { - &:is(:where(.group)[data-disabled="true"] *) { - pointer-events: none; + .group-data-\[copied\=true\]\:block { + &:is(:where(.group)[data-copied="true"] *) { + display: block; } } - .group-data-\[disabled\=true\]\:opacity-50 { - &:is(:where(.group)[data-disabled="true"] *) { - opacity: 50%; - } - } - .group-data-\[disabled\=true\]\/field\:opacity-50 { - &:is(:where(.group\/field)[data-disabled="true"] *) { - opacity: 50%; + .group-data-\[copied\=true\]\:hidden { + &:is(:where(.group)[data-copied="true"] *) { + display: none; } } .group-\[\.copied\]\:block { @@ -1466,16 +1393,6 @@ border-color: var(--color-primary); } } - .peer-disabled\:cursor-not-allowed { - &:is(:where(.peer):disabled ~ *) { - cursor: not-allowed; - } - } - .peer-disabled\:opacity-50 { - &:is(:where(.peer):disabled ~ *) { - opacity: 50%; - } - } .last\:border-b-0 { &:last-child { border-bottom-style: var(--tw-border-style); @@ -1534,13 +1451,6 @@ } } } - .hover\:bg-accent { - &:hover { - @media (hover: hover) { - background-color: var(--color-accent); - } - } - } .hover\:bg-accent\/50 { &:hover { @media (hover: hover) { @@ -1656,46 +1566,16 @@ pointer-events: none; } } - .disabled\:border { - &:disabled { - border-style: var(--tw-border-style); - border-width: 1px; - } - } .disabled\:opacity-50 { &:disabled { opacity: 50%; } } - .has-data-\[state\=checked\]\:border-primary { - &:has(*[data-state="checked"]) { - border-color: var(--color-primary); - } - } - .has-data-\[state\=checked\]\:bg-primary\/5 { - &:has(*[data-state="checked"]) { - background-color: var(--color-primary); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-primary) 5%, transparent); - } - } - } - .has-\[\:checkd\]\:border { - &:has(*:is(:checkd)) { - border-style: var(--tw-border-style); - border-width: 1px; - } - } .has-\[\:checked\]\:border-primary { &:has(*:is(:checked)) { border-color: var(--color-primary); } } - .has-\[\:checked\]\:bg-muted { - &:has(*:is(:checked)) { - background-color: var(--color-muted); - } - } .has-\[\:checked\]\:bg-primary\/5 { &:has(*:is(:checked)) { background-color: var(--color-primary); @@ -1704,12 +1584,6 @@ } } } - .has-\[input\:checkd\]\:border { - &:has(*:is(input:checkd)) { - border-style: var(--tw-border-style); - border-width: 1px; - } - } .has-\[input\[type\=\'checkbox\'\]\:checked\]\:border-blue-600 { &:has(*:is(input[type='checkbox']:checked)) { border-color: var(--color-blue-600); @@ -1730,32 +1604,6 @@ background-color: var(--color-green-50); } } - .has-\[\>\[data-slot\=field\]\]\:w-full { - &:has(>[data-slot=field]) { - width: 100%; - } - } - .has-\[\>\[data-slot\=field\]\]\:flex-col { - &:has(>[data-slot=field]) { - flex-direction: column; - } - } - .has-\[\>\[data-slot\=field\]\]\:rounded-md { - &:has(>[data-slot=field]) { - border-radius: var(--radius-md); - } - } - .has-\[\>\[data-slot\=field\]\]\:border { - &:has(>[data-slot=field]) { - border-style: var(--tw-border-style); - border-width: 1px; - } - } - .has-\[\>svg\]\:px-2 { - &:has(>svg) { - padding-inline: calc(var(--spacing) * 2); - } - } .has-\[\>svg\]\:px-2\.5 { &:has(>svg) { padding-inline: calc(var(--spacing) * 2.5); @@ -1779,21 +1627,11 @@ background-color: transparent; } } - .aria-pressed\:text-blue-500 { - &[aria-pressed="true"] { - color: var(--color-blue-500); - } - } .aria-pressed\:text-blue-600 { &[aria-pressed="true"] { color: var(--color-blue-600); } } - .aria-pressed\:text-yellow-500 { - &[aria-pressed="true"] { - color: var(--color-yellow-500); - } - } .aria-pressed\:hover\:bg-accent { &[aria-pressed="true"] { &:hover { @@ -1985,18 +1823,6 @@ } } } - .dark\:hover\:bg-accent\/50 { - &:is(.dark *) { - &:hover { - @media (hover: hover) { - background-color: var(--color-accent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-accent) 50%, transparent); - } - } - } - } - } .dark\:hover\:bg-destructive\/20 { &:is(.dark *) { &:hover { @@ -2019,16 +1845,6 @@ } } } - .dark\:has-data-\[state\=checked\]\:bg-primary\/10 { - &:is(.dark *) { - &:has(*[data-state="checked"]) { - background-color: var(--color-primary); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-primary) 10%, transparent); - } - } - } - } .dark\:has-\[\:checked\]\:bg-primary\/10 { &:is(.dark *) { &:has(*:is(:checked)) { @@ -2330,19 +2146,6 @@ } } } - .\[disabled\]\:border { - &:is(disabled) { - border-style: var(--tw-border-style); - border-width: 1px; - } - } - .\[\&\>\*\]\:data-\[slot\=field\]\:p-4 { - &>* { - &[data-slot="field"] { - padding: calc(var(--spacing) * 4); - } - } - } .\[\&\>a\]\:underline { &>a { text-decoration-line: underline; @@ -2388,12 +2191,6 @@ } } } - .\[\&\>svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 { - &>svg:not([class*='size-']) { - width: calc(var(--spacing) * 3.5); - height: calc(var(--spacing) * 3.5); - } - } } :root { --radius: 0.625rem; @@ -2597,7 +2394,7 @@ justify-content: center; gap: calc(var(--spacing) * 1); overflow: hidden; - border-radius: var(--radius-md); + border-radius: calc(infinity * 1px); border-style: var(--tw-border-style); border-width: 1px; padding-inline: calc(var(--spacing) * 2); @@ -3909,7 +3706,7 @@ } } @layer components { - kbd { + .kbd { pointer-events: none; display: inline-flex; height: calc(var(--spacing) * 5); diff --git a/docs/src/components/command.njk b/docs/src/components/command.njk index f98c155..3f468a3 100644 --- a/docs/src/components/command.njk +++ b/docs/src/components/command.njk @@ -225,7 +225,7 @@ toc:

Dialog

-

You can use some simple JavaScript to open the command menu with a keyboard shortcut (in this case ⌘J).

+

You can use some simple JavaScript to open the command menu with a keyboard shortcut (in this case ⌘J).

{% set code_dialog %} @@ -235,7 +235,7 @@ toc: onclick="document.getElementById('demo-command-dialog').showModal()" > Open command menu - ⌘J + ⌘J {% call command_dialog(id="demo-command-dialog") %} diff --git a/docs/src/components/empty.njk b/docs/src/components/empty.njk index 0da962b..c094e45 100644 --- a/docs/src/components/empty.njk +++ b/docs/src/components/empty.njk @@ -5,9 +5,6 @@ description: Display empty states with icons, titles, descriptions, and actions. toc: - label: Usage id: usage - children: - - label: HTML structure - id: html-structure - label: Examples id: examples children: @@ -58,57 +55,8 @@ toc:

Usage

-

The example simply uses Tailwind utility classes to create an empty state layout.

-
- -{{ code_block(code_default) }} - -

HTML structure

- -
-
-
<div>
-
Main container with centered layout utilities: -
    -
  • flex flex-col items-center justify-center: centers content
  • -
  • gap-6: spacing between sections
  • -
  • p-6 md:p-12: responsive padding
  • -
  • text-center text-balance: centered text with better wrapping
  • -
-
-
<header>
-
Header section containing the icon/avatar, title, and description: -
    -
  • max-w-sm: constrains width for readability
  • -
  • gap-2 or gap-3: spacing between elements
  • -
-
-
<div> Optional
-
Media container for icon or avatar: -
    -
  • For icons: bg-muted text-foreground size-10 rounded-lg
  • -
  • For avatars: size-12 rounded-full grayscale
  • -
  • For avatar groups: flex -space-x-2 with ring utilities
  • -
-
-
<h3>
-
Title with text-lg font-medium tracking-tight
-
<p>
-
Description with text-muted-foreground text-sm/relaxed
-
-
-
<section> Optional
-
Content area for actions (buttons, inputs, etc.): -
    -
  • max-w-sm or max-w-md: constrains width
  • -
  • flex flex-col items-center gap-3: vertical layout with spacing
  • -
-
-
<a> or other elements Optional
-
Additional links or actions outside the main content section
-
-
-
+

Empty states are pure HTML composition using Tailwind utility classes. Use flex layout utilities to center content, and compose with semantic elements like <header>, <h3>, <p>, and <button>.

+

Browse the examples below and copy the pattern that fits your needs.

Examples

@@ -186,7 +134,7 @@ toc:

Input Group

{% set code_input_group %} -
+

404 - Not Found

diff --git a/docs/src/components/input-group.njk b/docs/src/components/input-group.njk index 5f9bfcb..8e5d17e 100644 --- a/docs/src/components/input-group.njk +++ b/docs/src/components/input-group.njk @@ -173,8 +173,19 @@ toc:

- +
+
@@ -264,15 +275,15 @@ toc:
-
- {% lucide "loader-circle" %} +
+ {% lucide "loader-circle", { "class": "animate-spin" } %}
-
- {% lucide "loader-circle" %} +
+ {% lucide "loader-circle", { "class": "animate-spin" } %}
@@ -280,16 +291,16 @@ toc:
Saving...
-
- {% lucide "loader-circle" %} +
+ {% lucide "loader-circle", { "class": "animate-spin" } %}
-
- {% lucide "loader" %} +
+ {% lucide "loader-circle", { "class": "animate-spin" } %}
Please wait... diff --git a/docs/src/components/item.njk b/docs/src/components/item.njk index 6af2b36..eacbaeb 100644 --- a/docs/src/components/item.njk +++ b/docs/src/components/item.njk @@ -45,7 +45,7 @@ toc: -
-

4:05

+

4:05

@@ -200,7 +200,7 @@ toc:
-

3:30

+

3:30

diff --git a/docs/src/components/kbd.njk b/docs/src/components/kbd.njk index 5abbb02..da60653 100644 --- a/docs/src/components/kbd.njk +++ b/docs/src/components/kbd.njk @@ -20,15 +20,15 @@ toc: {% set code_html %}
- ⌘ - ⇧ - ⌥ - ⌃ + ⌘ + ⇧ + ⌥ + ⌃ - Ctrl + Ctrl + - B + B
{% endset %} @@ -38,11 +38,11 @@ toc:

Usage

-

Simply use the <kbd> element:

+

Use the <kbd> element with the kbd class:

{% set code_simple %} -⌘K +⌘K {% endset %} {{ code_block(code_simple | prettyHtml, "html") }} @@ -55,8 +55,8 @@ toc:

Use - Ctrl + B - Ctrl + K + Ctrl + B + Ctrl + K to open the command palette

@@ -68,8 +68,8 @@ toc: {% set button_html %}
- - + +
{% endset %} diff --git a/docs/src/components/progress.njk b/docs/src/components/progress.njk new file mode 100644 index 0000000..b86fb5a --- /dev/null +++ b/docs/src/components/progress.njk @@ -0,0 +1,44 @@ +--- +layout: layouts/page.njk +title: Progress +description: Displays an indicator showing the completion progress of a task, typically displayed as a progress bar. +toc: + - label: Usage + id: usage +--- + +{% from "macros/code_preview.njk" import code_preview %} +{% from "macros/code_block.njk" import code_block %} + +
+ {% lucide "circle-alert" %} +

There is no dedicated Progress component in Basecoat.

+
+ +{% set code_default %} +
+
+
+ +{% endset %} + +{{ code_preview("progress-default", code_default, class="w-full max-w-sm") }} + +

Usage

+ +
+

Progress bars are pure HTML composition using Tailwind utility classes. Use a container div for the track and an inner div for the indicator. Set the indicator's width with inline styles or via JavaScript.

+
+ +{% set code_usage %} +
+
+
+{% endset %} + +{{ code_block(code_usage) }} + diff --git a/docs/src/components/spinner.njk b/docs/src/components/spinner.njk new file mode 100644 index 0000000..6109b06 --- /dev/null +++ b/docs/src/components/spinner.njk @@ -0,0 +1,215 @@ +--- +layout: layouts/page.njk +title: Spinner +description: An indicator that can be used to show a loading state. +toc: + - label: Usage + id: usage + - label: Examples + id: examples + children: + - label: Size + id: example-size + - label: Color + id: example-color + - label: Button + id: example-button + - label: Badge + id: example-badge + - label: Input Group + id: example-input-group + - label: Empty + id: example-empty + - label: Item + id: example-item +--- + +{% from "macros/code_preview.njk" import code_preview %} +{% from "macros/code_block.njk" import code_block %} + +
+ {% lucide "circle-alert" %} +

There is no dedicated Spinner component in Basecoat.

+
+ +{% set code_default %} +
+
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %} +
+
+

Processing payment...

+
+
+

$100.00

+
+
+{% endset %} + +{{ code_preview("spinner-default", code_default, class="w-full max-w-xs") }} + +

Usage

+ +
+

Spinners are pure HTML using the loader-circle Lucide icon with the animate-spin Tailwind utility. Add role="status" and aria-label="Loading" for accessibility.

+

Browse the examples below and copy the pattern that fits your needs.

+
+ +

Examples

+ +

Size

+ +
+

Use the size-* utility class to change the size of the spinner.

+
+ +{% set code_size %} +
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-3 animate-spin" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-8 animate-spin" } %} +
+{% endset %} + +{{ code_preview("spinner-size", code_size) }} + +

Color

+ +
+

Use the text-* utility class to change the color of the spinner.

+
+ +{% set code_color %} +
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-red-500" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-green-500" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-blue-500" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-yellow-500" } %} + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-purple-500" } %} +
+{% endset %} + +{{ code_preview("spinner-color", code_color) }} + +

Button

+ +
+

Add a spinner to a button to indicate a loading state.

+
+ +{% set code_button %} +
+ + + +
+{% endset %} + +{{ code_preview("spinner-button", code_button) }} + +

Badge

+ +{% set code_badge %} +
+ + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %} + Syncing + + + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %} + Updating + + + {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %} + Processing + +
+{% endset %} + +{{ code_preview("spinner-badge", code_badge) }} + +

Input Group

+ +
+

Input groups can have spinners positioned inside them.

+
+ +{% set code_input_group %} +
+
+ +
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %} +
+
+ +
+ +
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin text-muted-foreground" } %} + Validating... + +
+
+
+{% endset %} + +{{ code_preview("spinner-input-group", code_input_group, class="w-full max-w-md") }} + +

Empty

+ +
+

Use a spinner in empty states to indicate a loading or processing state.

+
+ +{% set code_empty %} +
+
+
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4" } %} +
+

Processing your request

+

Please wait while we process your request. Do not refresh the page.

+
+
+ +
+
+{% endset %} + +{{ code_preview("spinner-empty", code_empty) }} + +

Item

+ +{% set code_item %} +
+
+ {% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4 text-muted-foreground" } %} +
+
+

Downloading...

+

129 MB / 1000 MB

+
+ + +
+{% endset %} + +{{ code_preview("spinner-item", code_item, class="w-full max-w-md") }} + diff --git a/src/css/basecoat.css b/src/css/basecoat.css index 3f5da68..0292f79 100644 --- a/src/css/basecoat.css +++ b/src/css/basecoat.css @@ -184,7 +184,7 @@ .badge-secondary, .badge-destructive, .badge-outline { - @apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden; + @apply inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden; } .badge, .badge-primary { @@ -747,7 +747,7 @@ /* Kbd */ @layer components { - kbd { + .kbd { @apply bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none [&_svg:not([class*='size-'])]:size-3; } }