Merge branch 'development'

This commit is contained in:
Ronan Berder
2025-11-05 11:37:34 +08:00
35 changed files with 4606 additions and 307 deletions
+1 -1
View File
@@ -70,6 +70,7 @@
<link rel="stylesheet" href="/assets/styles.css">
{# JS #}
<script src="/assets/js/basecoat.js" defer></script>
<script src="/assets/js/command.js" defer></script>
<script src="/assets/js/dropdown-menu.js" defer></script>
<script src="/assets/js/popover.js" defer></script>
<script src="/assets/js/select.js" defer></script>
@@ -98,7 +99,6 @@
<script>
document.addEventListener('htmx:historyRestore', () => {
if (window.basecoat) window.basecoat.initAll();
if (window.Alpine) Alpine.initTree(document.body);
});
</script>
{% if head %}{{ head | safe }}{% endif %}
+2 -2
View File
@@ -1,6 +1,6 @@
{% macro code_block(code, language="html", class="my-4") %}
<div class="relative {{ class }}">
<pre class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- code | escape -}}</code></pre>
<pre class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted/40 !p-3.5">{{- code | escape -}}</code></pre>
<button
onclick="
@@ -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" } %}
</button>
</div>
+20 -3
View File
@@ -61,6 +61,14 @@
"hx-swap": "outerHTML"
} %}
{% set new_link_attrs = {
"hx-boost": "true",
"hx-select": "#content",
"hx-target": "#content",
"hx-swap": "outerHTML",
"data-new-link": "true"
} %}
{% set icon_home %}{% lucide "house" %}{% endset %}
{% set icon_info %}{% lucide "info" %}{% endset %}
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
@@ -86,21 +94,30 @@
{ label: "Badge", url: "/components/badge", attrs: link_attrs },
{ label: "Breadcrumb", url: "/components/breadcrumb", attrs: link_attrs },
{ label: "Button", url: "/components/button", attrs: link_attrs },
{ label: "Button Group", url: "/components/button-group", attrs: new_link_attrs },
{ label: "Card", url: "/components/card", attrs: link_attrs },
{ label: "Checkbox", url: "/components/checkbox", attrs: link_attrs },
{ label: "Command", url: "/components/command", attrs: new_link_attrs },
{ label: "Combobox", url: "/components/combobox", attrs: link_attrs },
{ label: "Dialog", url: "/components/dialog", attrs: link_attrs },
{ label: "Dropdown Menu", url: "/components/dropdown-menu", attrs: link_attrs },
{ label: "Empty", url: "/components/empty", attrs: new_link_attrs },
{ label: "Field", url: "/components/field", attrs: new_link_attrs },
{ label: "Form", url: "/components/form", attrs: link_attrs },
{ label: "Input", url: "/components/input", attrs: link_attrs },
{ label: "Input Group", url: "/components/input-group", attrs: new_link_attrs },
{ label: "Item", url: "/components/item", attrs: new_link_attrs },
{ label: "Kbd", url: "/components/kbd", attrs: new_link_attrs },
{ 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: "Skeleton", url: "/components/skeleton", 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 },
@@ -117,6 +134,6 @@
label="Sidebar navigation",
header=header,
footer=footer,
content_attrs={ "class": "scrollbar" },
content_attrs={ "class": "scrollbar [&_[data-new-link]::after]:content-['New'] [&_[data-new-link]::after]:ml-auto [&_[data-new-link]::after]:text-xs [&_[data-new-link]::after]:font-medium [&_[data-new-link]::after]:bg-sidebar-primary [&_[data-new-link]::after]:text-sidebar-primary-foreground [&_[data-new-link]::after]:px-2 [&_[data-new-link]::after]:py-0.5 [&_[data-new-link]::after]:rounded-md" },
menu=menu
) }}
) }}
+1237 -203
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -11,7 +11,7 @@ toc:
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated accordion component in Basecoat.</h2>
<h2>There is no dedicated Accordion component in Basecoat.</h2>
</div>
{% set code %}<section class="accordion">
@@ -72,4 +72,4 @@ toc:
<h2 id="usage"><a href="#usage">Usage</a></h2>
<p class="prose">Basecoat animates <code>&lt;details&gt;</code> elements by default. The example above uses Alpine.js to handle the open/close state along with some Tailwind CSS classes.</p>
<p class="prose">Basecoat already animates <code>&lt;details&gt;</code> elements by default. The example add some Tailwind CSS utility classes for style and a bit of vanilla JavaScript to handle the open/close state.</p>
+1 -1
View File
@@ -43,7 +43,7 @@ toc:
<p>The Alert Dialog component is identical to <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> except for two small differences: we do not display a close button and we do not let the user close the dialog when clicking on the backdrop (no <code>onclick</code> attribute on the <code>&lt;dialog&gt;</code> or the <code>&lt;article&gt;</code> elements).</p>
</section>
{{ code_block(code_html | prettyHtml, class="") }}
{{ code_block(code_html | prettyHtml) }}
<section class="prose">
<p>The component has the following HTML structure:</p>
+47 -14
View File
@@ -19,30 +19,63 @@ toc:
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% set code %}<div class="alert">
{% lucide "circle-check" %}
<h2>Success! Your changes have been saved</h2>
<section>This is an alert with icon, title and description.</section>
</div>{% endset %}
{{ code_preview("alert", code) }}
{% set code %}
<div class="grid w-full max-w-xl items-start gap-4">
<div class="alert">
{% lucide "circle-check" %}
<h2>Success! Your changes have been saved</h2>
<section>This is an alert with icon, title and description.</section>
</div>
<div class="alert">
{% lucide "popcorn" %}
<h2>This Alert has a title and an icon. No description.</h2>
<section>This is an alert with icon, title and description.</section>
</div>
<div class="alert-destructive">
{% lucide "circle-check" %}
<h2>Unable to process your payment.</h2>
<section>
<p>Please verify your billing information and try again.</p>
<ul class="list-inside list-disc text-sm">
<li>Check your card details</li>
<li>Ensure sufficient funds</li>
<li>Verify billing address</li>
</ul>
</section>
</div>
</div>
{% endset %}
{{ code_preview("alert", code | prettyHtml) }}
<h2 id="usage">Usage</h2>
<section class="prose">
<p>You can use one of the two classes: <code>alert</code> or <code>alert-dialog</code>.</p>
<p>Use the <code>alert</code> or <code>alert-destructive</code>.</p>
</section>
{{ code_block(code) }}
{% set code_simple %}
<div class="alert">
{% lucide "circle-check" %}
<h2>Success! Your changes have been saved</h2>
<section>This is an alert with icon, title and description.</section>
</div>
{% endset %}
{{ code_block(code_simple | prettyHtml) }}
<section class="prose">
<p>The component has the following HTML structure:</p>
<dl>
<dt><code class="highlight language-html">&lt;svg&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>The icon.</dd>
<dt><code class="highlight language-html">&lt;h2&gt;</code></dt>
<dd>The title.</dd>
<dt><code class="highlight language-html">&lt;section&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>The description.</dd>
<dt><code class="highlight language-html">&lt;div class="alert"&gt;</code></dt>
<dd>Main container. Use <code>alert</code> for default styling or <code>alert-destructive</code> for error states.
<dl>
<dt><code class="highlight language-html">&lt;svg&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>The icon.</dd>
<dt><code class="highlight language-html">&lt;h2&gt;</code></dt>
<dd>The title.</dd>
<dt><code class="highlight language-html">&lt;section&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>The description.</dd>
</dl>
</dd>
</dl>
</section>
+21 -4
View File
@@ -12,12 +12,29 @@ toc:
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated avatar component in Basecoat.</h2>
<h2>There is no dedicated Avatar component in Basecoat.</h2>
</div>
{% set code %}<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">{% endset %}
{{ code_preview("avatar", code, "max-w-md") }}
{% set code %}
<div class="flex flex-row flex-wrap items-center gap-12">
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
<img class="size-8 shrink-0 object-cover rounded-lg" alt="@shadcn" src="https://github.com/shadcn.png">
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
<img alt="@shadcn" src="https://github.com/shadcn.png">
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
</div>
</div>
{% endset %}
{{ code_preview("avatar", code | prettyHtml, "w-full max-w-md") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<p class="prose">Avatars are fairly simple to implement with Tailwind. The example above shows how to implement one with Tailwind and an <code>&lt;img&gt;</code> element. More examples can be found in the <a href="/kitchen-sink#avatar">kitchen sink</a>.</p>
<section class="prose">
<p>Avatars are just <code>&lt;img&gt;</code> elements styled with Tailwind utility classes.</p>
</section>
{% set code_simple %}
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
{% endset %}
{{ code_block(code_simple | prettyHtml, "html") }}
+19 -1
View File
@@ -25,7 +25,25 @@ toc:
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% set code %}<span class="badge">Badge</span>{% endset %}
{% set code %}
<div class="flex flex-col items-center gap-2">
<div class="flex w-full flex-wrap gap-2">
<span class="badge">Badge</span>
<span class="badge-secondary">Secondary</span>
<span class="badge-destructive">Destructive</span>
<span class="badge-outline">Outline</span>
</div>
<div class="flex w-full flex-wrap gap-2">
<span class="badge-secondary bg-blue-500 text-white dark:bg-blue-600">
{% lucide "badge-check" %}
Verified
</span>
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">8</span>
<span class="badge-destructive rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">99</span>
<span class="badge-outline rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">20+</span>
</div>
</div>
{% endset %}
{{ code_preview("badge", code) }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
+14 -3
View File
@@ -13,7 +13,7 @@ toc:
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated breadcrumb component in Basecoat.</h2>
<h2>There is no dedicated Breadcrumb component in Basecoat.</h2>
</div>
{% set code_html %}
@@ -54,7 +54,18 @@ toc:
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>This example uses some simple Tailwind CSS styles along with the <a href="/components/dropdown-menu">Dropdown Menu</a> component (which may require JavaScript).</p>
<p>You can style an ordered list of links combined with chevron icons as show below. The example above demonstrates a more complex use case using the <a href="/components/dropdown-menu">Dropdown Menu</a> component.</p>
</section>
{{ code_block(code_html | prettyHtml, class="") }}
{% set code_simple %}
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
<li class="inline-flex items-center gap-1.5">
<a href="#" class="hover:text-foreground transition-colors">Home</a>
</li>
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
<li class="inline-flex items-center gap-1.5">
<a href="#" class="hover:text-foreground transition-colors">Components</a>
</li>
</ol>
{% endset %}
{{ code_block(code_simple | prettyHtml, "html") }}
+290
View File
@@ -0,0 +1,290 @@
---
layout: layouts/page.njk
title: Button Group
description: A container that groups related buttons together with consistent styling.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Orientation
id: example-orientation
- label: Nested
id: example-nested
- label: Separator
id: example-separator
- label: Input
id: example-input
- label: Dropdown Menu
id: example-dropdown-menu
- label: Select
id: example-select
- label: Popover
id: example-popover
---
{% from "dropdown-menu.njk" import dropdown_menu %}
{% from "popover.njk" import popover %}
{% from "select.njk" import select %}
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% set code_default %}
<div class="flex w-fit items-stretch gap-2">
<button type="button" class="btn-icon-outline" aria-label="Go Back">{% lucide "arrow-left" %}</button>
<div role="group" class="button-group">
<button type="button" class="btn-outline">Archive</button>
<button type="button" class="btn-outline">Report</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn-outline">Snooze</button>
{% set trigger %}
{% lucide "ellipsis" %}
{% endset %}
{% call dropdown_menu(
trigger=trigger,
trigger_attrs={"class": "btn-icon-outline"},
popover_attrs={"data-align": "end"}
) %}
<div role="menuitem">
{% lucide "mail-check" %}
Mark as Read
</div>
<div role="menuitem">
{% lucide "archive" %}
Archive
</div>
<hr role="separator">
<div role="menuitem">
{% lucide "clock" %}
Snooze
</div>
<div role="menuitem">
{% lucide "calendar" %}
Add to Calendar
</div>
<div role="menuitem">
{% lucide "list-filter-plus" %}
Add to List
</div>
<hr role="separator">
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
{% lucide "trash-2" %}
Trash
</div>
{% endcall %}
</div>
{% endset %}
{{ code_preview("button-group-default", code_default | prettyHtml) }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<p class="prose">Simply wrap buttons you want to group together in a <code>&lt;div role="group" class="button-group"&gt;</code> element. You can also use the <code>data-orientation="vertical"</code> attribute to stack buttons vertically. You can also group buttons with <a href="#example-dropdown-menu">dropdown menus</a> or <a href="#example-popover">popovers</a> (provided the trigger is a button). Finally, you can use a <a href="#example-separator">separator</a> to visually divide buttons within a group (for secondary buttons for example).</p>
{% set code_simple %}
<div role="group" class="button-group">
<button type="button" class="btn-outline">Archive</button>
<button type="button" class="btn-outline">Report</button>
</div>
{% endset %}
{{ code_block(code_simple | prettyHtml, "html") }}
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-orientation"><a href="#example-orientation">Orientation</a></h3>
<section class="prose">
<p>Use <code>data-orientation="vertical"</code> to stack buttons vertically.</p>
</section>
{% set code_orientation %}
<div role="group" class="button-group" data-orientation="vertical">
<button type="button" class="btn-icon-outline" aria-label="Zoom in">
{% lucide "plus" %}
</button>
<button type="button" class="btn-icon-outline" aria-label="Zoom out">
{% lucide "minus" %}
</button>
</div>
{% endset %}
{{ code_preview("button-group-orientation", code_orientation | prettyHtml) }}
<h3 id="example-size"><a href="#example-size">Size</a></h3>
{% set code_size %}
<div class="flex flex-col items-start gap-8">
<div role="group" class="button-group">
<button type="button" class="btn-sm-outline">Small</button>
<button type="button" class="btn-sm-outline">Button</button>
<button type="button" class="btn-sm-outline">Group</button>
<button type="button" class="btn-sm-icon-outline">{% lucide "plus" %}</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn-outline">Default</button>
<button type="button" class="btn-outline">Button</button>
<button type="button" class="btn-outline">Group</button>
<button type="button" class="btn-icon-outline">{% lucide "plus" %}</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn-lg-outline">Small</button>
<button type="button" class="btn-lg-outline">Button</button>
<button type="button" class="btn-lg-outline">Group</button>
<button type="button" class="btn-lg-icon-outline">{% lucide "plus" %}</button>
</div>
</div>
{% endset %}
{{ code_preview("button-group-size", code_size | prettyHtml) }}
<h3 id="example-nested"><a href="#example-nested">Nested</a></h3>
{% set code_nested %}
<div role="group" aria-label="Pagination controls" class="flex w-fit items-stretch gap-2">
<div role="group" class="button-group">
<button type="button" class="btn-sm-outline">1</button>
<button type="button" class="btn-sm-outline">2</button>
<button type="button" class="btn-sm-outline">3</button>
<button type="button" class="btn-sm-outline">4</button>
<button type="button" class="btn-sm-outline">5</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn-sm-icon-outline" aria-label="Previous page">
{% lucide "chevron-left" %}
</button>
<button type="button" class="btn-sm-icon-outline" aria-label="Next page">
{% lucide "chevron-right" %}
</button>
</div>
</div>
{% endset %}
{{ code_preview("button-group-nested", code_nested | prettyHtml) }}
<h3 id="example-separator"><a href="#example-separator">Separator</a></h3>
<section class="prose">
<p>Use <code>&lt;hr role="separator"&gt;</code> to visually divide buttons within a group.</p>
</section>
{% set code_separator %}
<div role="group" class="button-group">
<button type="button" class="btn-secondary">Copy</button>
<hr role="separator">
<button type="button" class="btn-secondary">Paste</button>
</div>
{% endset %}
{{ code_preview("button-group-separator", code_separator | prettyHtml) }}
<h3 id="example-input"><a href="#example-input">Input</a></h3>
{% set code_input %}
<div class="button-group">
<input type="text" class="input" placeholder="Search...">
<button type="button" class="btn-icon-outline">{% lucide "search" %}</button>
</div>
{% endset %}
{{ code_preview("button-group-input", code_input | prettyHtml) }}
<h3 id="example-dropdown-menu"><a href="#example-dropdown-menu">Dropdown Menu</a></h3>
{% set code_dropdown_menu %}
<div role="group" class="button-group">
<button type="button" class="btn-outline">Follow</button>
{% set trigger %}
{% lucide "chevron-down" %}
{% endset %}
{% call dropdown_menu(
trigger=trigger,
trigger_attrs={"class": "btn-icon-outline"},
popover_attrs={"data-align": "end"}
) %}
<div role="menuitem">
{% lucide "volume-off" %}
Mute Conversation
</div>
<div role="menuitem">
{% lucide "check" %}
Mark as Read
</div>
<div role="menuitem">
{% lucide "triangle-alert" %}
Report Conversation
</div>
<div role="menuitem">
{% lucide "user-round-x" %}
Block User
</div>
<div role="menuitem">
{% lucide "share" %}
Share Conversation
</div>
<div role="menuitem">
{% lucide "copy" %}
Copy Conversation
</div>
<hr role="separator">
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
{% lucide "trash-2" %}
Delete Conversation
</div>
{% endcall %}
</div>
{% endset %}
{{ code_preview("button-group-dropdown-menu", code_dropdown_menu | prettyHtml) }}
<h3 id="example-select"><a href="#example-select">Select</a></h3>
{% set code_select %}
<div class="flex w-fit items-stretch gap-2">
<div class="button-group">
{% call select(trigger_attrs={"class": "[&_[data-name]]:hidden"}) %}
<div role="option" data-value="$">$ <span class="text-muted-foreground" data-name>US Dollar</span></div>
<div role="option" data-value="€">€ <span class="text-muted-foreground" data-name>Euro</span></div>
<div role="option" data-value="£">£ <span class="text-muted-foreground" data-name>British Pound</span></div>
{% endcall %}
<input type="text" class="input" placeholder="10.00">
</div>
<button type="button" class="btn-icon-outline">{% lucide "arrow-right" %}</button>
</div>
{% endset %}
{{ code_preview("button-group-select", code_select | prettyHtml) }}
<h3 id="example-popover"><a href="#example-popover">Popover</a></h3>
{% set code_popover %}
<div role="group" class="button-group">
<button type="button" class="btn-outline">
{% lucide "bot" %}
Copilot
</button>
{% call popover(
trigger=trigger,
trigger_attrs={"class": "btn-icon-outline"},
popover_attrs={"class": "w-72 p-0", "data-align": "end"}
) %}
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
<section class="p-4 text-sm space-y-2">
<textarea class="textarea w-full mb-4" placeholder="Describe your taks in natural language"></textarea>
<p class="font-medium">Start a new task with Copilot</p>
<p class="text-muted-foreground">
Describe your task in natural language. Copilot will work in the
background and open a pull request for your review.
</p>
</section>
{% endcall %}
</div>
{% endset %}
{{ code_preview("button-group-popover", code_popover | prettyHtml) }}
+261
View File
@@ -0,0 +1,261 @@
---
layout: layouts/page.njk
title: Command
description: Fast, composable command menu for quick navigation and actions.
toc:
- label: Usage
id: usage
children:
- label: HTML + JavaScript
id: usage-html-js
children:
- label: "Step 1: Include the JavaScript files"
id: usage-html-js-1
- label: "Step 2: Add your command HTML"
id: usage-html-js-2
- label: HTML structure
id: usage-html-js-3
- label: JavaScript events
id: usage-html-js-4
- label: Jinja and Nunjucks
id: usage-macro
- label: Examples
id: examples
children:
- label: Dialog
id: example-dialog
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% from "command.njk" import command, command_dialog %}
{% macro command_items(suggestions_id="suggestions", settings_id="settings") %}
<div role="group" aria-labelledby="{{ suggestions_id }}">
<span role="heading" id="{{ suggestions_id }}">Suggestions</span>
<div role="menuitem" data-value="calendar" onclick="console.log('Open calendar')">
{% lucide "calendar" %}
<span>Calendar</span>
</div>
<div role="menuitem" data-value="search-emoji" onclick="console.log('Search emoji')">
{% lucide "smile" %}
<span>Search Emoji</span>
</div>
<div role="menuitem" data-value="calculator" aria-disabled="true">
{% lucide "calculator" %}
<span>Calculator</span>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="{{ settings_id }}">
<span role="heading" id="{{ settings_id }}">Settings</span>
<div role="menuitem" data-value="profile" onclick="console.log('Open profile')">
{% lucide "user" %}
<span>Profile</span>
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘P</kbd>
</div>
<div role="menuitem" data-value="billing" onclick="console.log('Open billing')">
{% lucide "credit-card" %}
<span>Billing</span>
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘B</kbd>
</div>
<div role="menuitem" data-value="settings" onclick="console.log('Open settings')">
{% lucide "settings" %}
<span>Settings</span>
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘S</kbd>
</div>
</div>
{% endmacro %}
{% set code_standalone %}
{% call command(
id="demo-command-standalone",
main_attrs={ "class": "rounded-lg border shadow-md" }
) %}
{{ command_items("suggestions", "settings") }}
{% endcall %}
{% endset %}
{{ code_preview("command-standalone", code_standalone | prettyHtml, class=" w-full max-w-[450px]") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
<section class="prose">
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code>&lt;head&gt;</code> of your page:</p>
</section>
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/command.min.js" defer></script>{% endset %}
{{ code_block(code_script | prettyHtml, "html") }}
<div class="flex flex-wrap gap-2 my-6">
<a class="badge-outline" href="/installation/#install-js">
Components with JavaScript
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="/installation/#install-cli">
Use the CLI
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/command.js" target="_blank">
command.js
{% lucide "arrow-right" %}
</a>
</div>
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your command HTML</a></h4>
<section class="prose">
<p>You can use the command menu standalone:</p>
</section>
{{ code_block(code_standalone | prettyHtml, "html") }}
<section class="prose">
<p>Wrap the command menu in a <code>&lt;dialog class="command-dialog"&gt;</code> to display in a dialog:</p>
</section>
{% set code_dialog %}
<dialog class="command-dialog" aria-label="Command menu">
<div class="command">
<!-- Regular command content: header, menu, etc. -->
</div>
</dialog>
{% endset %}
{{ code_block(code_dialog | prettyHtml, "html") }}
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
<section class="prose">
<dl>
<dt><code class="highlight language-html">&lt;dialog class="command-dialog"&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>For dialog variant, wraps a <code>&lt;div class="command"&gt;</code> to display as a modal. Uses native <code>&lt;dialog&gt;</code> behavior and requires <code>aria-label</code> or <code>aria-labelledby</code> for accessibility.
<dl>
<dt><code class="highlight language-html">&lt;div class="command"&gt;</code></dt>
<dd>Wraps around the entire command component. Can be used standalone or inside a dialog.
<dl>
<dt><code class="highlight language-html">&lt;header&gt;</code></dt>
<dd>Contains the search input to filter command options.
<dl>
<dt><code class="highlight language-html">&lt;input type="text"&gt;</code></dt>
<dd>The search input. Should have:
<ul>
<li><code>role="combobox"</code>: indicates the input controls a menu with filtering</li>
<li><code>aria-expanded="true"</code>: the menu is always expanded</li>
<li><code>aria-controls="{ MENU_ID }"</code>: points to the menu's id</li>
</ul>
</dd>
</dl>
</dd>
<dt><code class="highlight language-html">&lt;div role="menu"&gt;</code></dt>
<dd>The menu containing the command items. Should have <code>id="{ MENU_ID }"</code> refered to by the input's <code>aria-controls</code> attribute.
<dl>
<dt><code class="highlight language-html">&lt;div role="menuitem"&gt;</code></dt>
<dd>Command item that triggers an action when clicked. In a command-dialog, clicking a menuitem automatically closes the dialog unless the <code>data-keep-command-open</code> attribute is present. Optional attributes:
<ul>
<li><code>data-label</code>: custom label for filtering (if different from text content)</li>
<li><code>data-keywords</code>: additional keywords for enhanced filtering</li>
<li><code>aria-disabled="true"</code>: marks item as disabled (excluded from keyboard navigation and filtering)</li>
</ul>
</dd>
<dt><code class="highlight language-html">&lt;hr role="separator"&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Separator between groups/items. Hidden when filtering is active.</dd>
<dt><code class="highlight language-html">&lt;div role="group"&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Group of items, can have <code>aria-labelledby</code> attribute to link to a heading.</dd>
<dt><code class="highlight language-html">&lt;span role="heading"&gt;</code></dt>
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
</section>
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
<section class="prose">
<dl>
<dt><code>basecoat:initialized</code></dt>
<dd>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
</dl>
</section>
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
<div class="prose">
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">command()</code> or <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">command_dialog()</code> Nunjucks or Jinja macros for this component.</p>
</div>
<div class="flex flex-wrap gap-2 my-6">
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
Use Nunjucks or Jinja macros
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/command.html.jinja" target="_blank">
Jinja macro
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk" target="_blank">
Nunjucks macro
{% lucide "arrow-right" %}
</a>
</div>
{% set raw_code %}{% raw %}{% call command() %}
<div role="menuitem" data-value="calendar">
<svg>...</svg>
<span>Calendar</span>
</div>
<div role="menuitem" data-value="settings">
<svg>...</svg>
<span>Settings</span>
</div>
{% endcall %}{% endraw %}{% endset %}
{{ code_block(raw_code, "jinja") }}
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-dialog"><a href="#example-dialog">Dialog</a></h3>
<section class="prose">
<p>You can use some simple JavaScript to open the command menu with a keyboard shortcut (in this case <kbd class="kbd">⌘J</kbd>).</p>
</section>
{% set code_dialog %}
<button
type="button"
class="btn-outline"
onclick="document.getElementById('demo-command-dialog').showModal()"
>
Open command menu
<kbd class="kbd">⌘J</kbd>
</button>
{% call command_dialog(id="demo-command-dialog") %}
{{ command_items("cmd-suggestions", "cmd-settings") }}
{% endcall %}
<script>
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'j') {
e.preventDefault();
const dialog = document.getElementById('demo-command-dialog');
if (dialog.open) {
dialog.close();
} else {
dialog.showModal();
dialog.querySelector('header input')?.focus();
}
}
});
</script>
{% endset %}
{{ code_preview("command-dialog", code_dialog | prettyHtml) }}
+2 -2
View File
@@ -69,8 +69,8 @@ toc:
<li><code>onclick</code>: closes the dialog when the backdrop is clicked.</li>
</ul>
<dl>
<dt><code class="highlight language-html">&lt;article&gt;</code></dt>
<dd>Wraps the content of the dialog. It includes an <code>onclick</code> attribute to prevent from closing the dialog if we have an <code>onclick</code> event on <code>&lt;dialog&gt;</code>.
<dt><code class="highlight language-html">&lt;div&gt;</code></dt>
<dd>Wraps the content of the dialog.
<dl>
<dt><code class="highlight language-html">&lt;header&gt;</code></dt>
<dd>Contains the header for the dialog:
+157
View File
@@ -0,0 +1,157 @@
---
layout: layouts/page.njk
title: Empty
description: Display empty states with icons, titles, descriptions, and actions.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Outline
id: example-outline
- label: Avatar
id: example-avatar
- label: Avatar Group
id: example-avatar-group
- label: Input Group
id: example-input-group
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Empty component in Basecoat.</h2>
</div>
{% set code_default %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-2 text-center">
<div class="mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6">
{% lucide "folder-code" %}
</div>
<h3 class="text-lg font-medium tracking-tight">No Projects Yet</h3>
<p class="text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4">
You haven't created any projects yet. Get started by creating your first project.
</p>
</header>
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance">
<div class="flex gap-2">
<button class="btn">Create Project</button>
<button class="btn-outline">Import Project</button>
</div>
</section>
<a href="#" class="inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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 underline-offset-4 hover:underline h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 text-muted-foreground">
Learn More
{% lucide "arrow-up-right" %}
</a>
</div>
{% endset %}
{{ code_preview("empty-default", code_default) }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Empty states are pure HTML composition using Tailwind utility classes. Use flex layout utilities to center content, and compose with semantic elements like <code>&lt;header&gt;</code>, <code>&lt;h3&gt;</code>, <code>&lt;p&gt;</code>, and <code>&lt;button&gt;</code>.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
{% set code_outline %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300 border">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<div class="mb-2 bg-muted text-foreground flex size-12 shrink-0 items-center justify-center rounded-lg [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6">
{% lucide "cloud" %}
</div>
<h3 class="text-lg font-semibold tracking-tight">Cloud Storage Empty</h3>
<p class="text-muted-foreground text-sm/relaxed">
Upload files to your cloud storage to access them anywhere.
</p>
</header>
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
<button class="btn-sm-outline">Upload Files</button>
</section>
</div>
{% endset %}
{{ code_preview("empty-outline", code_outline) }}
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
{% set code_avatar %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<div class="mb-2 flex items-center justify-center">
<img alt="@shadcn" src="https://github.com/shadcn.png" class="grayscale size-12 shrink-0 object-cover rounded-full">
</div>
<h3 class="text-lg font-semibold tracking-tight">User Offline</h3>
<p class="text-muted-foreground text-sm/relaxed">
This user is currently offline. You can leave a message to notify them or try again later.
</p>
</header>
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
<button class="btn-sm">Leave Message</button>
</section>
</div>
{% endset %}
{{ code_preview("empty-avatar", code_avatar) }}
<h3 id="example-avatar-group"><a href="#example-avatar-group">Avatar Group</a></h3>
{% set code_avatar_group %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<div class="mb-2 flex items-center justify-center">
<div class="flex -space-x-2 [&amp;_img]:ring-background [&amp;_img]:ring-2 [&amp;_img]:grayscale [&amp;_img]:size-12 [&amp;_img]:shrink-0 [&amp;_img]:object-cover [&amp;_img]:rounded-full">
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
<img alt="@shadcn" src="https://github.com/shadcn.png">
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
</div>
</div>
<h3 class="text-lg font-semibold tracking-tight">No Team Members</h3>
<p class="text-muted-foreground text-sm/relaxed">
Invite your team to collaborate on this project.
</p>
</header>
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
<button class="btn-sm inline-flex items-center gap-2">
{% lucide "plus" %}
Invite Members
</button>
</section>
</div>
{% endset %}
{{ code_preview("empty-avatar-group", code_avatar_group) }}
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
{% set code_input_group %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<h3 class="text-lg font-semibold tracking-tight">404 - Not Found</h3>
<p class="text-muted-foreground text-sm/relaxed">
The page you're looking for doesn't exist. Try searching for what you need below.
</p>
</header>
<section class="flex w-full max-w-md min-w-0 flex-col items-center gap-3">
<div class="relative w-3/4">
<input type="text" class="input pl-8.5 pr-10" placeholder="Try searching for pages...">
{% lucide "search" , { "class": "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground pointer-events-none shrink-0 size-4" } %}
<kbd class="kbd absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">/</kbd>
</div>
<p class="text-muted-foreground text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary">
Need help? <a href="#">Contact support</a>
</p>
</section>
</div>
{% endset %}
{{ code_preview("empty-input-group", code_input_group) }}
+459
View File
@@ -0,0 +1,459 @@
---
layout: layouts/page.njk
title: Field
description: Combine labels, controls, and help text to compose accessible form fields.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Input
id: example-input
- label: Textarea
id: example-textarea
- label: Select
id: example-select
- label: Fieldset
id: example-fieldset
- label: Checkbox
id: example-checkbox
- label: Radio Group
id: example-radio
- label: Switch
id: example-switch
- label: Responsive Layout
id: example-responsive
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% set code_demo %}
<form class="w-full max-w-md space-y-6">
<fieldset class="fieldset">
<legend>Payment Method</legend>
<p>All transactions are secure and encrypted</p>
<div role="group" class="field">
<label for="card-name">Name on Card</label>
<input id="card-name" type="text" placeholder="Evil Rabbit" required>
</div>
<div role="group" class="field">
<label for="card-number">Card Number</label>
<input id="card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="card-number-desc" required>
<p id="card-number-desc">Enter your 16-digit card number</p>
</div>
<div class="grid grid-cols-3 gap-4">
<div role="group" class="field">
<label for="exp-month">Month</label>
<select id="exp-month" class="select w-full">
<option value="">MM</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="08">08</option>
<option value="09">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
</div>
<div role="group" class="field">
<label for="exp-year">Year</label>
<select id="exp-year" class="select w-full">
<option value="">YYYY</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
<option value="2026">2026</option>
<option value="2027">2027</option>
<option value="2028">2028</option>
<option value="2029">2029</option>
</select>
</div>
<div role="group" class="field">
<label for="cvv">CVV</label>
<input id="cvv" type="text" placeholder="123" required>
</div>
</div>
</fieldset>
<hr role="separator" class="my-6 border-border">
<fieldset class="fieldset">
<legend>Billing Address</legend>
<p>The billing address associated with your payment method</p>
<div role="group" class="field">
<label for="same-as-shipping" class="gap-3">
<input type="checkbox" id="same-as-shipping" checked>
Same as shipping address
</label>
</div>
</fieldset>
<fieldset class="fieldset">
<div role="group" class="field">
<label for="comments">Comments</label>
<textarea id="comments" placeholder="Add any additional comments" rows="3"></textarea>
</div>
</fieldset>
<div class="flex gap-3">
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn-outline">Cancel</button>
</div>
</form>
{% endset %}
{{ code_preview("field-demo", code_demo) }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Field uses semantic HTML with minimal classes. Two main patterns can be used independently or composed together:</p>
<ul>
<li><code>&lt;div role="group" class="field"&gt;</code> for individual field containers (label, input, helper text, errors)</li>
<li><code>&lt;fieldset class="fieldset"&gt;</code> for grouping multiple related fields together</li>
</ul>
<p>Elements inside these containers are styled automatically based on their purpose. Use <code>aria-describedby</code> to connect helper text and errors to inputs for accessibility.</p>
<p><strong>Fields</strong> have the following HTML structure:</p>
<dl>
<dt><code class="highlight language-html">&lt;div role="group" class="field"&gt;</code></dt>
<dd>Field container. Add <code>data-orientation="horizontal"</code> to align controls horizontally. For responsive layouts, use standard Tailwind responsive classes.
<dl>
<dt><code class="highlight language-html">&lt;label&gt;</code></dt>
<dd>Label for the input.</dd>
<dt><code class="highlight language-html">&lt;input&gt;</code>, <code>&lt;select&gt;</code> or <code>&lt;textarea&gt;</code></dt>
<dd>Form input.</dd>
<dt><code class="highlight language-html">&lt;p&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Helper text. Use <code>aria-describedby</code> on the input to reference this element's <code>id</code>.</dd>
<dt><code class="highlight language-html">&lt;p role="alert"&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Error message. Use <code>aria-describedby</code> on the input and <code>aria-invalid="true"</code> to reference this element's <code>id</code>.</dd>
<dt><code class="highlight language-html">&lt;section&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Wraps label and description when the label sits beside the input (e.g. <code>data-orientation="horizontal"</code>). Not required if you have no description.</dd>
</dl>
</dd>
</dl>
<p><strong>Fieldsets</strong> have the following HTML structure:</p>
<dl>
<dt><code class="highlight language-html">&lt;fieldset class="fieldset"&gt;</code></dt>
<dd>Container for grouping related fields.
<dl>
<dt><code class="highlight language-html">&lt;legend&gt;</code></dt>
<dd>Heading for the fieldset.</dd>
<dt><code class="highlight language-html">&lt;p&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Description text. When placed directly after <code>&lt;legend&gt;</code>, automatically styled as muted text.</dd>
<dt><code class="highlight language-html">&lt;div role="group" class="field"&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Individual field containers (see structure above). You can include multiple <code>.field</code> elements, or place form controls directly in the fieldset.</dd>
</dl>
</dd>
</dl>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-input"><a href="#example-input">Input</a></h3>
{% set code_input %}
<div class="grid gap-6">
<div role="group" class="field">
<label for="username">Username</label>
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc">
<p id="username-desc">Choose a unique username for your account.</p>
</div>
<div role="group" class="field">
<label for="password">Password</label>
<p id="password-desc">Must be at least 8 characters long.</p>
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc">
</div>
</div>
{% endset %}
{{ code_preview("field-input", code_input, class="w-full max-w-md") }}
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
{% set code_textarea %}
<div role="group" class="field">
<label for="bio">Feedback</label>
<textarea id="bio" rows="4" placeholder="Your feedback helps us improve..." aria-describedby="bio-desc"></textarea>
<p id="bio-desc">Share your thoughts about our service.</p>
</div>
{% endset %}
{{ code_preview("field-textarea", code_textarea, class="w-full max-w-md") }}
<h3 id="example-select"><a href="#example-select">Select</a></h3>
{% set code_select %}
<div role="group" class="field">
<label for="country">Department</label>
<select id="country" class="select w-full" aria-describedby="country-desc">
<option value="">Choose department</option>
<option value="engineering">Engineering</option>
<option value="design">Design</option>
<option value="marketing">Marketing</option>
<option value="sales">Sales</option>
<option value="support">Customer Support</option>
<option value="hr">Human Resources</option>
<option value="finance">Finance</option>
<option value="operations">Operations</option>
</select>
<p id="country-desc">Select your department or area of work.</p>
</div>
{% endset %}
{{ code_preview("field-select", code_select, class="w-full max-w-md") }}
<h3 id="example-slider"><a href="#example-slider">Slider</a></h3>
{% set code %}
<div role="group" class="field">
<label for="price-range">Price</label>
<p id="price-range-desc">Set your budget: $<span id="price-range-value">150</span></p>
<input id="price-range" type="range" min="0" max="500" value="150">
</div>
<script>
(() => {
const sliders = document.querySelectorAll('input[type="range"]');
if (!sliders) return;
const updateSlider = (el) => {
const min = parseFloat(el.min || 0);
const max = parseFloat(el.max || 100);
const value = parseFloat(el.value);
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
el.style.setProperty('--slider-value', `${percent}%`);
};
sliders.forEach(slider => {
updateSlider(slider);
slider.addEventListener('input', (event) => updateSlider(event.target));
});
const priceSlider = document.getElementById('price-range');
const priceValue = document.getElementById('price-range-value');
priceSlider.addEventListener('input', (event) => priceValue.innerHTML = event.target.value);
})();
</script>
{% endset %}
{{ code_preview("slider", code, "w-full max-w-sm") }}
<h3 id="example-fieldset"><a href="#example-fieldset">Fieldset</a></h3>
<section class="prose">
<p>Group related fields with <code>&lt;fieldset class="fieldset"&gt;</code> and <code>&lt;legend&gt;</code>.</p>
</section>
{% set code_fieldset %}
<form class="w-full max-w-md">
<fieldset class="fieldset">
<legend>Profile</legend>
<p>This information will be displayed on your profile</p>
<div role="group" class="field">
<label for="full-name">Full name</label>
<input id="full-name" type="text" placeholder="Evil Rabbit" aria-describedby="name-desc">
<p id="name-desc">Your first and last name</p>
</div>
<div role="group" class="field">
<label for="display-name">Username</label>
<input id="display-name" type="text" placeholder="@evilrabbit" aria-invalid="true" aria-describedby="username-error">
<p id="username-error" role="alert">Username is already taken</p>
</div>
<div class="flex items-center gap-3">
<input type="checkbox" id="newsletter">
<label for="newsletter" class="font-normal">Subscribe to the newsletter</label>
</div>
</fieldset>
<button type="submit" class="btn mt-4">Save changes</button>
</form>
{% endset %}
{{ code_preview("field-fieldset", code_fieldset) }}
<h3 id="example-checkbox"><a href="#example-checkbox">Checkbox</a></h3>
{% set code_checkbox %}
<fieldset class="fieldset">
<legend>Show these items on the desktop</legend>
<p>Select the items you want to show on the desktop.</p>
<div class="flex flex-col gap-3">
<div class="field">
<label class="gap-3">
<input type="checkbox">
Hard disks
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
External disks
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
CDs, DVDs and iPods
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="checkbox">
Connected servers
</label>
</div>
</div>
<hr role="separator">
<div class="field" data-orientation="horizontal">
{# <div class="flex w-full items-center gap-3"> #}
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start">
<section>
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
<p>Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
</section>
{# </div> #}
</div>
</fieldset>
{% endset %}
{{ code_preview("field-checkbox", code_checkbox, class="w-full max-w-md") }}
<h3 id="example-radio"><a href="#example-radio">Radio Group</a></h3>
{% set code_radio %}
<fieldset class="fieldset">
<legend>Subscription plan</legend>
<p>Yearly and lifetime plans offer significant savings.</p>
<div role="radiogroup" class="grid gap-3">
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan" checked>
Monthly ($9.99/month)
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan">
Yearly ($99.99/year)
</label>
</div>
<div class="field">
<label class="gap-3">
<input type="radio" name="subscription-plan">
Lifetime ($299.99)
</label>
</div>
</div>
</fieldset>
{% endset %}
{{ code_preview("field-radio", code_radio, class="w-full max-w-md") }}
<h3 id="example-switch"><a href="#example-switch">Switch</a></h3>
<section class="prose">
<p>Set <code>data-orientation="horizontal"</code> on <code>.field</code> to align labels and controls side-by-side.</p>
</section>
{% set code_switch %}
<div role="group" class="field" data-orientation="horizontal">
<section>
<label for="multi-factor-authentication">Multi-factor authentication</label>
<p>Enable multi-factor authentication. If you do not have a two-factor device, you can use a one-time code sent to your email.</p>
</section>
<input id="multi-factor-authentication" type="checkbox" role="switch">
</div>
{% endset %}
{{ code_preview("field-switch", code_switch, class="w-full max-w-md") }}
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
{% set code_choice_card %}
<fieldset class="fieldset">
<legend class="text-sm">Compute Environment</legend>
<p>Select the compute environment for your cluster.</p>
<div role="group" class="field">
<label class="flex gap-3 border rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
<section>
<h3>Kubernetes</h3>
<p>Run GPU workloads on a K8s configured cluster.</p>
</section>
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked>
</label>
</div>
<div role="group" class="field">
<label class="flex gap-3 border rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
<section>
<h3>Virtual Machine</h3>
<p>Access a VM configured cluster to run GPU workloads.</p>
</section>
<input id="choice-card" type="radio" name="choice-card" class="self-start">
</label>
</div>
</fieldset>
{% endset %}
{{ code_preview("field-choice-card", code_choice_card, class="w-full max-w-md") }}
<h3 id="example-responsive"><a href="#example-responsive">Responsive Layout</a></h3>
<section class="prose">
<p>For responsive layouts, use standard Tailwind responsive classes directly on <code>.field</code>. This example stacks vertically on mobile and switches to horizontal at the <code>md</code> breakpoint.</p>
</section>
{% set code_responsive %}
<fieldset class="fieldset">
<legend>Profile</legend>
<p>Keep your profile details up to date.</p>
<hr role="separator">
<div class="flex flex-col gap-7">
<div role="group" class="field flex-col md:flex-row md:items-center">
<section class="md:flex-auto">
<label for="profile-name">Name</label>
<p id="profile-name-desc">Provide your full name for identification.</p>
</section>
<input id="profile-name" type="text" placeholder="Evil Rabbit" aria-describedby="profile-name-desc" class="md:w-auto md:min-w-80">
</div>
<hr role="separator">
<div role="group" class="field flex-col md:flex-row md:items-start">
<section class="md:flex-auto">
<label for="profile-bio">Message</label>
<p id="profile-bio-desc">You can write your message here. Keep it short, preferably under 100 characters.</p>
</section>
<textarea id="profile-bio" rows="3" placeholder="Hello, world!" aria-describedby="profile-bio-desc" class="md:w-auto md:min-w-80"></textarea>
</div>
<hr role="separator">
<div role="group" class="field flex-col md:flex-row md:items-center">
<button type="submit" class="btn md:w-auto">Submit</button>
<button type="button" class="btn-outline md:w-auto">Cancel</button>
</div>
</div>
</fieldset>
{% endset %}
{{ code_preview("field-responsive", code_responsive, class="w-full max-w-3xl") }}
+372
View File
@@ -0,0 +1,372 @@
---
layout: layouts/page.njk
title: Input Group
description: Display additional information or actions to an input or textarea.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Icon
id: example-icon
- label: Text
id: example-text
- label: Button
id: example-button
- label: Tooltip
id: example-tooltip
- label: Textarea
id: example-textarea
- label: Spinner
id: example-spinner
- label: Dropdown
id: example-dropdown
- label: Group
id: example-group
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% from "dropdown-menu.njk" import dropdown_menu %}
{% from "popover.njk" import popover %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Input Group component in Basecoat.</h2>
</div>
{% set code_default %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-9 pr-20" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "search" %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">12 results</div>
</div>
<div class="relative">
<input type="text" class="input pl-15 pr-9" placeholder="example.com">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">https://</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="This is content in a tooltip.">{% lucide "info" %}</div>
</div>
<div class="relative">
<textarea class="textarea pr-10 min-h-27 pb-12" placeholder="Ask, Search or Chat..."></textarea>
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
<button type="button" class="btn-icon-outline rounded-full size-6 text-muted-foreground hover:text-accent-foreground">
{% lucide "plus" %}
</button>
{{ dropdown_menu(
id="demo-dropdown-menu",
trigger="Auto",
trigger_attrs={"class": "btn-sm-ghost text-muted-foreground hover:text-accent-foreground h-6 p-2"},
popover_attrs={"data-side": "top", "class": "min-w-32"},
items=[
{ type: "item", label: "Auto" },
{ type: "item", label: "Agent" },
{ type: "item", label: "Manual" }
]
) }}
<div class="text-muted-foreground text-sm ml-auto">52% used</div>
<hr class="w-0 h-4 border-r border-border shrink-0 m-0">
<button type="button" class="btn-icon rounded-full size-6 bg-muted-foreground hover:bg-foreground" disabled>
{% lucide "arrow-up" %}
</button>
</footer>
</div>
<div class="relative">
<input type="text" class="input pr-9" placeholder="@shadcn">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full [&>svg]:size-3">{% lucide "check" %}</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-default", code_default, class="w-full max-w-sm") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Input groups are pure HTML composition using Tailwind utility classes. There is no dedicated component: use <code>relative</code> positioning on a wrapper, add your content with <code>absolute</code> positioning, and adjust input padding to make room.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
{% set code_icon %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-9" placeholder="Search...">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "search" %}
</div>
</div>
<div class="relative">
<input type="email" class="input pl-9" placeholder="Enter your email">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "mail" %}
</div>
</div>
<div class="relative">
<input type="text" class="input px-9" placeholder="Card number">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "credit-card" %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "check" %}
</div>
</div>
<div class="relative">
<input type="text" class="input pr-16" placeholder="Card number">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 flex items-center gap-2">
{% lucide "star" %}
{% lucide "info" %}
</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-icon-prefix", code_icon, class="w-full max-w-sm") }}
<h3 id="example-text"><a href="#example-text">Text</a></h3>
{% set code_text %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pl-7 pr-12" placeholder="0.00">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">$</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">USD</div>
</div>
<div class="relative">
<input type="text" class="input pl-15 pr-12" placeholder="example.com">
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">https://</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">.com</div>
</div>
<div class="relative">
<input type="text" class="input pr-30" placeholder="Enter your username">
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">@company.com</div>
</div>
<div class="relative">
<textarea class="textarea pr-10 min-h-25 pb-12" placeholder="Enter your message"></textarea>
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
<div class="text-muted-foreground text-sm">120 characters left</div>
</footer>
</div>
</div>
{% endset %}
{{ code_preview("input-group-text", code_text, class="w-full max-w-sm") }}
<h3 id="example-button"><a href="#example-button">Button</a></h3>
{% set code_button %}
<div class="grid gap-6">
<div class="relative">
<input type="text" readonly class="input pr-9" value="https://x.com/hunvreus">
<button
data-copied="false"
onclick="
this.dataset.copied = 'true';
setTimeout(() => this.dataset.copied = 'false', 2000);
"
class="group absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6"
>
{% lucide "copy", { "class": "group-data-[copied=true]:hidden" } %}
{% lucide "check", { "class": "hidden group-data-[copied=true]:block" } %}
</button>
</div>
<div class="relative">
<input type="text" class="input pl-21 pr-9 rounded-full">
<button
aria-pressed="false"
onclick="this.ariaPressed = this.ariaPressed === 'true' ? 'false' : 'true'"
class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6 rounded-full aria-pressed:bg-transparent aria-pressed:hover:bg-accent dark:aria-pressed:hover:bg-accent/50 aria-pressed:text-blue-600 aria-pressed:[&>svg]:fill-blue-600"
>{% lucide "star" %}</button>
<div class="absolute left-1.5 top-1/2 -translate-y-1/2 flex items-center gap-1 z-10">
{% set trigger %}
{% lucide "info" %}
{% endset %}
{% call popover(
trigger=trigger,
trigger_attrs={"class": "btn-sm-icon-ghost rounded-full size-6"},
popover_attrs={"class": "max-w-72"}
) %}
<h3 class="font-medium mb-1">Your connection is not secure.</h3>
<p>You should not enter any sensitive information on this site.</p>
{% endcall %}
<div class="text-muted-foreground text-sm pointer-events-none">https://</div>
</div>
</div>
<div class="relative">
<input type="text" readonly class="input pr-9" placeholder="Type to search...">
<button type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-secondary h-6 p-2 rounded">Search</button>
</div>
</div>
{% endset %}
{{ code_preview("input-group-button", code_button, class="w-full max-w-sm") }}
<h3 id="example-tooltip"><a href="#example-tooltip">Tooltip</a></h3>
{% set code_tooltip %}
<div class="grid gap-6">
<div class="relative">
<input type="password" class="input pr-9" placeholder="Enter password">
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="Password must be at least 8 characters long.">{% lucide "info" %}</div>
</div>
<div class="relative">
<input type="email" class="input pr-9" placeholder="Your email address">
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="We'll use this to send your notifications.">{% lucide "circle-help" %}</div>
</div>
<div class="relative">
<input type="email" class="input pl-9" placeholder="Your email address">
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="Click for help with API keys." data-side="left">{% lucide "circle-help" %}</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-tooltip", code_tooltip, class="w-full max-w-sm") }}
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
{% set code_textarea %}
<div class="relative">
<textarea class="textarea pt-15 pb-17 min-h-77" placeholder="console.log('Hello, world!')."></textarea>
<header class="absolute top-0 flex items-center w-full gap-2 p-3 border-b">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-brand-javascript size-4 text-muted-foreground"><path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path><path d="M7.5 8h3v8l-2 -1"></path><path d="M16.5 8h-2.5a.5 .5 0 0 0 -.5 .5v3a.5 .5 0 0 0 .5 .5h1.423a.5 .5 0 0 1 .495 .57l-.418 2.93l-2 .5"></path></svg>
<span class="font-mono text-sm text-muted-foreground mr-auto">script.js</span>
<button class="btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">
{% lucide "refresh-ccw" %}
</button>
<button class="btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">
{% lucide "copy" %}
</button>
</header>
<footer class="absolute bottom-0 flex items-center w-full gap-2 p-3 border-t">
<span class="text-sm text-muted-foreground mr-auto">Line 1, Column 1</span>
<button type="button" class="btn-sm">
Run
{% lucide "corner-down-left" %}
</button>
</footer>
</div>
{% endset %}
{{ code_preview("input-group-textarea", code_textarea, class="w-full max-w-md") }}
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
{% set code_spinner %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Searching..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
</div>
<div class="relative">
<input type="text" class="input pl-9" placeholder="Processing..." disabled>
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
</div>
<div class="relative">
<input type="text" class="input pr-24" placeholder="Saving changes..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
<div>Saving...</div>
<div class="[&>svg]:size-4">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
</div>
</div>
<div class="relative">
<input type="text" class="input pl-9 pr-26" placeholder="Refreshing data..." disabled>
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
{% lucide "loader", { "class": "animate-spin" } %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
Please wait...
</div>
</div>
</div>
{% endset %}
{{ code_preview("input-group-spinner", code_spinner, class="w-full max-w-md") }}
<h3 id="example-dropdown"><a href="#example-dropdown">Dropdown</a></h3>
{% set code_dropdown %}
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Enter file name">
{% set trigger %}
{% lucide "ellipsis" %}
{% endset %}
{{ dropdown_menu(
main_attrs={"class": "absolute right-1.5 top-1/2 -translate-y-1/2 z-10"},
trigger=trigger,
trigger_attrs={"class": "btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6"},
popover_attrs={"data-align": "end", "class": "min-w-32"},
items=[
{ type: "item", label: "Settings" },
{ type: "item", label: "Copy path" },
{ type: "item", label: "Open location" }
]
) }}
</div>
<div class="relative">
<input type="text" class="input pr-30" placeholder="Enter search query">
{% set trigger %}
Search in...
{% lucide "chevron-down", { "class": "size-3" } %}
{% endset %}
{{ dropdown_menu(
main_attrs={"class": "absolute right-1.5 top-1/2 -translate-y-1/2"},
trigger=trigger,
trigger_attrs={"class": "btn-sm-ghost text-muted-foreground hover:text-accent-foreground p-2 h-6"},
popover_attrs={"data-align": "end", "class": "min-w-32"},
items=[
{ type: "item", label: "Documentation" },
{ type: "item", label: "Blog Posts" },
{ type: "item", label: "Changelog" }
]
) }}
</div>
</div>
{% endset %}
{{ code_preview("input-group-dropdown", code_dropdown, class="w-full max-w-sm") }}
<h3 id="example-group"><a href="#example-group">Group</a></h3>
{% set code_group %}
<div class="flex w-fit items-stretch">
<label for="url" class="text-sm flex items-center bg-muted border border-r-0 px-4 rounded-l-md shadow-xs">https://</label>
<div class="relative">
<input type="text" class="input pr-9 rounded-none">
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4">{% lucide "link-2" %}</div>
</div>
<div class="text-sm flex items-center bg-muted border border-l-0 px-4 rounded-r-md shadow-xs">.com</div>
</div>
{% endset %}
{{ code_preview("input-group-group", code_group, class="w-full max-w-sm") }}
+320
View File
@@ -0,0 +1,320 @@
---
layout: layouts/page.njk
title: Item
description: A versatile component that you can use to display any content.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Variants
id: example-variants
- label: Size
id: example-size
- label: Icon
id: example-icon
- label: Avatar
id: example-avatar
- label: Image
id: example-image
- label: Group
id: example-group
- label: Header
id: example-header
- label: Link
id: example-link
---
{% from "macros/code_block.njk" import code_block %}
{% from "macros/code_preview.njk" import code_preview %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Item component in Basecoat.</h2>
</div>
{% set code_basic %}
<div class="flex flex-col gap-6">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Basic Item</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">A simple item with title and description.</p>
</div>
<button class="btn-sm-outline">Action</button>
</article>
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border py-3 px-4 gap-2.5">
<div class="flex shrink-0 items-center justify-center gap-2 [&_svg]:pointer-events-none bg-transparent [&_svg]:size-5">
{% lucide "badge-check" %}
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Your profile has been verified.</h3>
</div>
<div class="flex items-center gap-2 [&_svg]:size-4">
{% lucide "chevron-right" %}
</div>
</a>
</div>
{% endset %}
{{ code_preview("item-basic", code_basic | prettyHtml, class="w-full max-w-md") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Items are pure HTML composition using Tailwind utility classes. Use semantic elements like <code>&lt;article&gt;</code> or <code>&lt;a&gt;</code>, apply flex layout, and compose with standard HTML elements like <code>&lt;h3&gt;</code>, <code>&lt;p&gt;</code>, <code>&lt;img&gt;</code>, or <code>&lt;button&gt;</code>.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-variants"><a href="#example-variants">Variants</a></h3>
{% set code_variants %}
<div class="flex flex-col gap-6">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Default Variant</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Standard styling with subtle background and borders.</p>
</div>
<button class="btn-sm-outline">Open</button>
</article>
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Outline Variant</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Outlined style with clear borders and transparent background.</p>
</div>
<button class="btn-sm-outline">Open</button>
</article>
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent bg-muted/50 p-4 gap-4">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Muted Variant</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Subdued appearance with muted colors for secondary content.</p>
</div>
<button class="btn-sm-outline">Open</button>
</article>
</div>
{% endset %}
{{ code_preview("item-variants", code_variants) }}
<h3 id="example-size"><a href="#example-size">Size</a></h3>
<section class="prose">
<p>Use different padding and gap sizes for compact or spacious layouts.</p>
</section>
{{ code_preview("item-size", code_basic) }}
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
{% set code_icon %}
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex shrink-0 items-center justify-center gap-2 self-start [&_svg]:pointer-events-none size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4">
{% lucide "shield-alert" %}
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Security Alert</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">New login detected from unknown device.</p>
</div>
<button class="btn-sm-outline">Review</button>
</article>
{% endset %}
{{ code_preview("item-icon", code_icon, class="max-w-lg") }}
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
{% set code_avatar %}
<div class="flex flex-col gap-6">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img src="https://github.com/hunvreus.png" alt="hunvreus" class="size-10 rounded-full object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">hunvreus</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Last seen 5 months ago</p>
</div>
<button class="btn-icon-outline rounded-full">
{% lucide "plus" %}
</button>
</article>
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full self-start">
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
<img alt="@shadcn" src="https://github.com/shadcn.png">
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">No Team Members</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Invite your team to collaborate on this project.</p>
</div>
<button class="btn-outline">
Invite
</button>
</article>
</div>
{% endset %}
{{ code_preview("item-avatar", code_avatar, class="w-full max-w-lg") }}
<h3 id="example-image"><a href="#example-image">Image</a></h3>
{% set code_image %}
<div class="flex flex-col gap-6 w-full max-w-md">
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img src="https://avatar.vercel.sh/example_1" alt="Midnight City Lights" class="grayscale size-10 rounded-sm object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Midnight City Lights - <span class="text-muted-foreground">Electric Nights</span></h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Neon Dreams</p>
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:45</p>
</div>
</a>
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img src="https://avatar.vercel.sh/example_2" alt="Midnight City Lights" class="grayscale size-10 rounded-sm object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Coffee Shop Conversations - <span class="text-muted-foreground">Urban Stories</span></h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">The Morning Brew</p>
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">4:05</p>
</div>
</a>
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img src="https://avatar.vercel.sh/example_3" alt="Midnight City Lights" class="grayscale size-10 rounded-sm object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Digital Rain - <span class="text-muted-foreground">Binary Beats</span></h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Cyber Symphony</p>
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:30</p>
</div>
</a>
</div>
{% endset %}
{{ code_preview("item-image", code_image) }}
<h3 id="example-group"><a href="#example-group">Group</a></h3>
{% set code_group %}
<div class="flex flex-col">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
<img src="https://github.com/hunvreus.png" alt="Ronan Berder" class="grayscale size-10 rounded-full object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">hunvreus</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">hunvreus@gmail.com</p>
</div>
<button class="btn-icon-outline rounded-full">
{% lucide "plus" %}
</button>
</article>
<hr role="separator" class="border-border">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
<img src="https://github.com/shadcn.png" alt="shadcn" class="grayscale size-10 rounded-full object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">shadcn</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">shadcn@vercel.com</p>
</div>
<button class="btn-icon-outline rounded-full">
{% lucide "plus" %}
</button>
</article>
<hr role="separator" class="border-border">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
<img src="https://github.com/adamwathan.png" alt="Adam Wathan" class="grayscale size-10 rounded-full object-cover">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">adamwathan</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">adam.wathan@gmail.com</p>
</div>
<button class="btn-icon-outline rounded-full">
{% lucide "plus" %}
</button>
</article>
</div>
{% endset %}
{{ code_preview("item-group", code_group, class="w-full max-w-md") }}
<h3 id="example-header"><a href="#example-header">Header</a></h3>
{% set code_header %}
<div class="flex-col grid grid-cols-3 gap-4">
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img alt="v0-1.5-sm" loading="lazy" class="grayscale aspect-square w-full rounded-sm object-cover flex-basis" src="https://avatar.vercel.sh/v0-1.5-sm">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">v0-1.5-sm</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Everyday tasks and UI generation.</p>
</div>
</article>
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img alt="v0-1.5-lg" loading="lazy" class="grayscale aspect-square w-full rounded-sm object-cover flex-basis" src="https://avatar.vercel.sh/v0-1.5-lg">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">v0-1.5-lg</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Advanced thinking or reasoning.</p>
</div>
</article>
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<img alt="v0-2.0-mini" loading="lazy" class="grayscale aspect-square w-full rounded-sm object-cover flex-basis" src="https://avatar.vercel.sh/v0-2.0-mini">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">v0-2.0-mini</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Open Source model for everyone.</p>
</div>
</article>
</div>
{% endset %}
{{ code_preview("item-header", code_header) }}
<h3 id="example-link"><a href="#example-link">Link</a></h3>
<section class="prose">
<p>Items can be rendered as links by using an <code>&lt;a&gt;</code> element. Hover and focus states are applied automatically.</p>
</section>
{% set code_link %}
<div class="flex flex-col gap-6">
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Visit our documentation</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Learn how to get started with our components.</p>
</div>
<div class="flex items-center gap-2 [&_svg]:size-4">
{% lucide "chevron-right" %}
</div>
</a>
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">External resource</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Opens in a new tab with security attributes.</p>
</div>
<div class="flex items-center gap-2 [&_svg]:size-4">
{% lucide "external-link" %}
</div>
</a>
</div>
{% endset %}
{{ code_preview("item-link", code_link, class="w-full max-w-md") }}
+76
View File
@@ -0,0 +1,76 @@
---
layout: layouts/page.njk
title: Kbd
description: Used to display textual user input from keyboard.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Group
id: example-group
- label: Button
id: example-button
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
{% set code_html %}
<div class="flex flex-col items-center gap-4">
<span class="inline-flex items-center gap-1">
<kbd class="kbd">⌘</kbd>
<kbd class="kbd">⇧</kbd>
<kbd class="kbd">⌥</kbd>
<kbd class="kbd">⌃</kbd>
</span>
<span class="inline-flex items-center gap-1">
<kbd class="kbd">Ctrl</kbd>
<span>+</span>
<kbd class="kbd">B</kbd>
</span>
</div>
{% endset %}
{{ code_preview("kbd", code_html | prettyHtml) }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Use the <code>&lt;kbd&gt;</code> element with the <code>kbd</code> class:</p>
</section>
{% set code_simple %}
<kbd class="kbd">⌘K</kbd>
{% endset %}
{{ code_block(code_simple | prettyHtml, "html") }}
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-group"><a href="#example-group">Group</a></h3>
{% set group_html %}
<p class="text-muted-foreground text-sm">
Use
<span class="inline-flex items-center gap-1">
<kbd class="kbd">Ctrl + B</kbd>
<kbd class="kbd">Ctrl + K</kbd>
</span>
to open the command palette
</p>
{% endset %}
{{ code_preview("kbd-group", group_html | prettyHtml) }}
<h3 id="example-button"><a href="#example-button">Button</a></h3>
{% set button_html %}
<div class="flex flex-wrap items-center gap-x-4">
<button class="btn-sm-outline pr-2">Accept <kbd class="kbd">⏎</kbd></button>
<button class="btn-sm-outline pr-2">Cancel <kbd class="kbd">Esc</kbd></button>
</div>
{% endset %}
{{ code_preview("kbd-button", button_html | prettyHtml) }}
+44
View File
@@ -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 %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Progress component in Basecoat.</h2>
</div>
{% set code_default %}
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 17%"></div>
</div>
<script>
setTimeout(() => {
document.getElementById('demo-progress').style.width = '66%';
}, 500);
</script>
{% endset %}
{{ code_preview("progress-default", code_default, class="w-full max-w-sm") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>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.</p>
</section>
{% set code_usage %}
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
<div class="bg-primary h-full w-full flex-1 transition-all" style="width: 66%"></div>
</div>
{% endset %}
{{ code_block(code_usage) }}
+214
View File
@@ -0,0 +1,214 @@
---
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 %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Spinner component in Basecoat.</h2>
</div>
{% set code_default %}
<article class="group/item flex items-center border text-sm rounded-md transition-colors flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent bg-muted/50 p-4 gap-4">
<div class="flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:size-4">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Processing payment...</h3>
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">$100.00</p>
</div>
</article>
{% endset %}
{{ code_preview("spinner-default", code_default, class="w-full max-w-xs") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Spinners are pure HTML using the <code>loader-circle</code> Lucide icon with the <code>animate-spin</code> Tailwind utility. Add <code>role="status"</code> and <code>aria-label="Loading"</code> for accessibility.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-size"><a href="#example-size">Size</a></h3>
<section class="prose">
<p>Use the <code>size-*</code> utility class to change the size of the spinner.</p>
</section>
{% set code_size %}
<div class="flex items-center gap-6">
{% 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" } %}
</div>
{% endset %}
{{ code_preview("spinner-size", code_size) }}
<h3 id="example-color"><a href="#example-color">Color</a></h3>
<section class="prose">
<p>Use the <code>text-*</code> utility class to change the color of the spinner.</p>
</section>
{% set code_color %}
<div class="flex items-center gap-6">
{% 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" } %}
</div>
{% endset %}
{{ code_preview("spinner-color", code_color) }}
<h3 id="example-button"><a href="#example-button">Button</a></h3>
<section class="prose">
<p>Add a spinner to a button to indicate a loading state.</p>
</section>
{% set code_button %}
<div class="flex flex-col items-center gap-4">
<button class="btn-sm" disabled>
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Loading...
</button>
<button class="btn-sm-outline" disabled>
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Please wait
</button>
<button class="btn-sm-secondary" disabled>
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Processing
</button>
</div>
{% endset %}
{{ code_preview("spinner-button", code_button) }}
<h3 id="example-badge"><a href="#example-badge">Badge</a></h3>
{% set code_badge %}
<div class="flex items-center gap-4">
<span class="badge">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Syncing
</span>
<span class="badge-secondary">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Updating
</span>
<span class="badge-outline">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Processing
</span>
</div>
{% endset %}
{{ code_preview("spinner-badge", code_badge) }}
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
<section class="prose">
<p>Input groups can have spinners positioned inside them.</p>
</section>
{% set code_input_group %}
<div class="flex w-full max-w-md flex-col gap-4">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Send a message..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
</div>
</div>
<div class="relative">
<textarea class="textarea pr-10 min-h-27 pb-12" placeholder="Send a message..." disabled></textarea>
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin text-muted-foreground" } %}
<span class="text-sm text-muted-foreground">Validating...</span>
<button type="button" class="btn-sm-icon h-6 w-7.5 ml-auto rounded">
{% lucide "arrow-up" %}
</button>
</footer>
</div>
</div>
{% endset %}
{{ code_preview("spinner-input-group", code_input_group, class="w-full max-w-md") }}
<h3 id="example-empty"><a href="#example-empty">Empty</a></h3>
<section class="prose">
<p>Use a spinner in empty states to indicate a loading or processing state.</p>
</section>
{% set code_empty %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<div class="mb-2 bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4" } %}
</div>
<h3 class="text-lg font-semibold tracking-tight">Processing your request</h3>
<p class="text-muted-foreground text-sm/relaxed">Please wait while we process your request. Do not refresh the page.</p>
</header>
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
<button class="btn-sm-outline">Cancel</button>
</section>
</div>
{% endset %}
{{ code_preview("spinner-empty", code_empty) }}
<h3 id="example-item"><a href="#example-item">Item</a></h3>
{% set code_item %}
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex shrink-0 items-center justify-center gap-2 self-start [&_svg]:pointer-events-none size-8 border rounded-md bg-muted [&_svg:not([class*='size-'])]:size-4">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4 text-muted-foreground" } %}
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Downloading...</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">129 MB / 1000 MB</p>
</div>
<button class="btn-sm-outline self-start">Cancel</button>
<footer class="flex basis-full items-center justify-between gap-2">
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 13%"></div>
</div>
</footer>
</article>
{% endset %}
{{ code_preview("spinner-item", code_item, class="w-full max-w-md") }}
+31
View File
@@ -29,8 +29,12 @@ toc:
id: install-cli
- label: Use Nunjucks or Jinja macros
id: install-macros
- label: Icons
id: install-icons
- label: Theming
id: install-theming
- label: Customization
id: install-customization
---
{% from "macros/code_block.njk" import code_block %}
@@ -192,6 +196,33 @@ import 'basecoat-css/tabs';{% endraw %}{% endset %}
{% endcall %}{% endraw %}{% endset %}
{{ code_block(code, "jinja") }}
<h2 id="install-icons"><a href="#install-icons">Icons</a></h2>
<div class="prose">
<p>Basecoat uses <a href="https://lucide.dev" target="_blank">Lucide icons</a>. You have three options:</p>
<ul>
<li><b>Copy SVG code</b>: Visit <a href="https://lucide.dev/icons" target="_blank">lucide.dev/icons</a>, click any icon to copy the SVG, and paste it directly in your HTML. Best for simple projects or occasional icon usage.</li>
<li><b>Install lucide</b>: Follow the <a href="https://lucide.dev/guide/installation" target="_blank">installation guide</a> to install the <code>lucide</code> package and dynamically render icons. Best for projects that need many icons or want tree-shaking.</li>
<li><b>Use framework packages</b>: Browse <a href="https://lucide.dev/packages" target="_blank">framework-specific packages</a> (React, Vue, Angular, etc.) for component-based icon usage. Best if you're using a supported framework.</li>
</ul>
</div>
<div class="flex flex-wrap gap-2 my-6">
<a class="badge-outline" href="https://lucide.dev" target="_blank">
Browse icons
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://lucide.dev/guide/installation" target="_blank">
Installation guide
{% lucide "arrow-right" %}
</a>
<a class="badge-outline" href="https://lucide.dev/packages" target="_blank">
Packages
{% lucide "arrow-right" %}
</a>
</div>
<h2 id="install-theming"><a href="#install-theming">Theming</a></h2>
<div class="prose">