Checkpoint repo restructure

This commit is contained in:
Ronan Berder
2026-06-27 09:01:35 +08:00
parent c81900a58d
commit f254b92bd3
124 changed files with 104 additions and 2455 deletions
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-luma.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/luma";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-lyra.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/lyra";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-maia.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/maia";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-mira.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/mira";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-nova.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/nova";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-rhea.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/rhea";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-sera.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/sera";
@@ -0,0 +1,8 @@
@import "tailwindcss" source(none);
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
@source "../../../../src/pages/**/*.{astro,html,js}";
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-vega.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/vega";
+309
View File
@@ -0,0 +1,309 @@
# Accordion
A vertically stacked set of interactive headings that each reveal a section of content.
<Preview class="w-full max-w-sm">
<section class="accordion">
<details open>
<summary>
What are your shipping options?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>We offer standard (5-7 days), express (2-3 days), and overnight shipping. Free shipping on international orders.</section>
</details>
<details>
<summary>
What is your return policy?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>You can return items within 30 days of delivery. Items must be unused and in their original packaging.</section>
</details>
<details>
<summary>
How can I contact customer support?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Email support@example.com or use live chat during business hours.</section>
</details>
</section>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Accordion component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/accordion.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Accordion script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/accordion.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your accordion HTML">
```html
<section class="accordion">
<details open>
<summary>
Is it accessible?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Yes. It uses native disclosure semantics.</section>
</details>
</section>
```
</Step>
</Steps>
Basecoat uses native `<details>` and `<summary>` elements for disclosure semantics. The accordion JavaScript only enforces single-item behavior by default and prevents disabled items from toggling. Add `data-multiple` to the root when more than one item can be open at a time.
### HTML structure
<dl>
<dt><code>&lt;section class="accordion"&gt;</code></dt>
<dd>
Root accordion container. Add <code>data-multiple</code> to allow multiple open items.
<dl>
<dt><code>&lt;details open&gt;</code></dt>
<dd>
Accordion item. Use the native <code>open</code> attribute for the default expanded item. Add <code>aria-disabled="true"</code> when an item should not toggle.
<dl>
<dt><code>&lt;summary&gt;</code></dt>
<dd>Accordion trigger. Keep the trigger text visible and add an optional trailing icon.</dd>
<dt><code>&lt;section&gt;</code></dt>
<dd>Accordion content. Use any semantic element that fits your content.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
### JavaScript API
Accordion does not expose custom methods or events. Programmatically open or close an item with the native `details.open` boolean.
```js
document.querySelector("#billing").open = true
```
## Examples
### Basic
A basic accordion that shows one item at a time. The first item is open by default.
<Preview class="w-full max-w-sm">
<section class="accordion">
<details open>
<summary>
How do I reset my password?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Click on 'Forgot Password' on the login page, enter your email address, and we'll send you a link to reset your password. The link will expire in 24 hours.</section>
</details>
<details>
<summary>
Can I change my subscription plan?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Yes. You can upgrade or downgrade your plan from the billing settings page.</section>
</details>
<details>
<summary>
What payment methods do you accept?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>We accept all major credit cards, PayPal, and bank transfers for annual plans.</section>
</details>
</section>
</Preview>
### Multiple
Use `data-multiple` to allow multiple items to be open at the same time.
<Preview class="w-full max-w-md">
<section class="accordion" data-multiple>
<details open>
<summary>
Notification Settings
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Manage how you receive notifications. You can enable email alerts for updates or push notifications for mobile devices.</section>
</details>
<details>
<summary>
Privacy & Security
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Control account security, session history, and privacy preferences.</section>
</details>
<details>
<summary>
Billing & Subscription
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Review invoices, change plans, and update payment methods.</section>
</details>
</section>
</Preview>
### Disabled
Use `aria-disabled="true"` on an item to disable it.
<Preview class="w-full max-w-md">
<section class="accordion">
<details open>
<summary>
Can I access my account history?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Yes. Your account history is available from the activity page.</section>
</details>
<details aria-disabled="true">
<summary>
Premium feature information
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>This item is disabled.</section>
</details>
<details>
<summary>
How do I update my email address?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Go to account settings, update your email, then confirm the change from your inbox.</section>
</details>
</section>
</Preview>
### Borders
Add horizontal padding to the rows when using a bordered root.
<Preview class="w-full max-w-md">
<section class="accordion rounded-md border [&>details>summary]:px-4 [&>details>:not(summary)]:px-4">
<details id="billing" open>
<summary>
How does billing work?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>We offer monthly and annual subscription plans. Billing is charged at the beginning of each cycle, and you can cancel anytime. All plans include automatic backups, 24/7 support, and unlimited team members.</section>
</details>
<details>
<summary>
Is my data secure?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Data is encrypted in transit and at rest.</section>
</details>
<details>
<summary>
What integrations do you support?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>We support Slack, GitHub, Google Workspace, and webhooks.</section>
</details>
</section>
</Preview>
### Card
Wrap the accordion in a card for grouped support or billing content.
<Preview class="w-full max-w-md">
<div class="card">
<header>
<h2>Subscription & Billing</h2>
<p>Common questions about your account, plans, payments and cancellations.</p>
</header>
<section>
<section class="accordion">
<details open>
<summary>
What subscription plans do you offer?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>We offer three subscription tiers: Starter ($9/month), Professional ($29/month), and Enterprise ($99/month). Each plan includes increasing storage limits, API access, priority support, and team collaboration features.</section>
</details>
<details>
<summary>
How does billing work?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Billing is charged at the beginning of each cycle.</section>
</details>
<details>
<summary>
How do I cancel my subscription?
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>Cancel from billing settings. Your plan remains active until the end of the current cycle.</section>
</details>
</section>
</section>
</div>
</Preview>
### RTL
Accordion spacing uses logical properties. Set `dir="rtl"` on the accordion or a parent element.
<Preview class="w-full max-w-md">
<section class="accordion" dir="rtl">
<details open>
<summary>
كيف يمكنني إعادة تعيين كلمة المرور؟
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>انقر على 'نسيت كلمة المرور' في صفحة تسجيل الدخول، أدخل عنوان بريدك الإلكتروني، وسنرسل لك رابطا لإعادة تعيين كلمة المرور. سينتهي صلاحية الرابط خلال 24 ساعة.</section>
</details>
<details>
<summary>
هل يمكنني تغيير خطة الاشتراك الخاصة بي؟
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>نعم، يمكنك تغيير الخطة من إعدادات الفوترة.</section>
</details>
<details>
<summary>
ما هي طرق الدفع التي تقبلونها؟
<svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</summary>
<section>نقبل بطاقات الائتمان الرئيسية والتحويلات البنكية.</section>
</details>
</section>
</Preview>
+187
View File
@@ -0,0 +1,187 @@
# Alert Dialog
A modal dialog that interrupts the user with important content and expects a response.
<Preview>
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn" data-variant="outline">Show Dialog</button>
<dialog id="demo-alert-dialog" class="alert-dialog" aria-labelledby="demo-alert-dialog-title" aria-describedby="demo-alert-dialog-description">
<div>
<header>
<h2 id="demo-alert-dialog-title">Are you absolutely sure?</h2>
<p id="demo-alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
</header>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
</footer>
</div>
</dialog>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Alert Dialog component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/dialog.css";
@import "basecoat-css/styles/vega.css";
```
Alert Dialog uses the Dialog component CSS.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your alert dialog HTML">
Alert Dialog uses a native `<dialog class="alert-dialog">` element. Omit the close button and backdrop-click handler when the user must choose an explicit action.
```html
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn" data-variant="outline">Show Dialog</button>
<dialog id="demo-alert-dialog" class="alert-dialog" aria-labelledby="demo-alert-dialog-title" aria-describedby="demo-alert-dialog-description">
<div>
<header>
<h2 id="demo-alert-dialog-title">Are you absolutely sure?</h2>
<p id="demo-alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
</header>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
</footer>
</div>
</dialog>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;button type="button" onclick="dialog.showModal()"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Trigger button. Basecoat uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
<dt><code>&lt;dialog class="alert-dialog" id="&#123; DIALOG_ID &#125;"&gt;</code></dt>
<dd>
Native modal dialog. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present.
<dl>
<dt><code>&lt;div&gt;</code></dt>
<dd>
Dialog content surface.
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>
Alert dialog header.
<dl>
<dt><code>&lt;figure&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Media area for an icon or image.</dd>
<dt><code>&lt;h2 id="&#123; TITLE_ID &#125;"&gt;</code></dt>
<dd>Alert dialog title. Reference it from <code>aria-labelledby</code>.</dd>
<dt><code>&lt;p id="&#123; DESCRIPTION_ID &#125;"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Alert dialog description. Reference it from <code>aria-describedby</code>.</dd>
</dl>
</dd>
<dt><code>&lt;footer&gt;</code></dt>
<dd>Action area. Include a cancel action and the primary action.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
## Examples
### Basic
A basic alert dialog with a title, description, and cancel and continue buttons.
<Preview>
<button type="button" onclick="document.getElementById('alert-dialog-basic').showModal()" class="btn" data-variant="outline">Show Dialog</button>
<dialog id="alert-dialog-basic" class="alert-dialog" aria-labelledby="alert-dialog-basic-title" aria-describedby="alert-dialog-basic-description">
<div>
<header>
<h2 id="alert-dialog-basic-title">Are you absolutely sure?</h2>
<p id="alert-dialog-basic-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
</header>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
</footer>
</div>
</dialog>
</Preview>
### Media
Use a `<figure>` in the header to add a media element such as an icon or image to the alert dialog.
<Preview>
<button type="button" onclick="document.getElementById('alert-dialog-media').showModal()" class="btn" data-variant="outline">Share Project</button>
<dialog id="alert-dialog-media" class="alert-dialog" aria-labelledby="alert-dialog-media-title" aria-describedby="alert-dialog-media-description">
<div>
<header>
<figure><svg class="lucide lucide-circle-fading-plus" 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"><path d="M12 2a10 10 0 0 1 7.38 16.75" /><path d="M12 8v8" /><path d="M16 12H8" /><path d="M2.5 8.88a10 10 0 0 0 0 6.24" /><path d="M4.64 4.64a10 10 0 0 0 0 14.72" /></svg></figure>
<h2 id="alert-dialog-media-title">Share this project?</h2>
<p id="alert-dialog-media-description">Anyone with the link will be able to view and edit this project.</p>
</header>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" onclick="this.closest('dialog').close()">Share</button>
</footer>
</div>
</dialog>
</Preview>
### Destructive
Use a destructive action button when the alert dialog confirms a destructive operation.
<Preview>
<button type="button" onclick="document.getElementById('alert-dialog-destructive').showModal()" class="btn" data-variant="destructive">Delete Chat</button>
<dialog id="alert-dialog-destructive" class="alert-dialog" aria-labelledby="alert-dialog-destructive-title" aria-describedby="alert-dialog-destructive-description">
<div>
<header>
<figure><svg class="lucide lucide-trash-2" 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"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg></figure>
<h2 id="alert-dialog-destructive-title">Delete this chat?</h2>
<p id="alert-dialog-destructive-description">This action cannot be undone. This will permanently delete the chat and remove it from your history.</p>
</header>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" data-variant="destructive" onclick="this.closest('dialog').close()">Delete</button>
</footer>
</div>
</dialog>
</Preview>
### RTL
Alert Dialog supports document direction. Set `dir="rtl"` on the dialog or a parent element.
<Preview>
<div dir="rtl">
<button type="button" onclick="document.getElementById('alert-dialog-rtl').showModal()" class="btn" data-variant="outline">إظهار الحوار</button>
<dialog id="alert-dialog-rtl" class="alert-dialog" aria-labelledby="alert-dialog-rtl-title" aria-describedby="alert-dialog-rtl-description">
<div>
<header>
<h2 id="alert-dialog-rtl-title">هل أنت متأكد تماماً؟</h2>
<p id="alert-dialog-rtl-description">لا يمكن التراجع عن هذا الإجراء. سيتم حذف حسابك نهائياً وإزالة بياناتك من خوادمنا.</p>
</header>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
<button class="btn" onclick="this.closest('dialog').close()">متابعة</button>
</footer>
</div>
</dialog>
</div>
</Preview>
+140
View File
@@ -0,0 +1,140 @@
# Alert
<Preview class="w-full max-w-md">
<div class="grid items-start gap-4">
<div class="alert"><svg class="lucide lucide-circle-check" 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"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
<h2>Payment successful</h2>
<section>Your payment of $29.99 has been processed. A receipt has been sent to your email address.</section>
</div>
<div class="alert"><svg class="lucide lucide-info" 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"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
<h2>New feature available</h2>
<section>We've added dark mode support. You can enable it in your account settings.</section>
</div>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Alert component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/alert.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your alert HTML">
Use `alert` for default styling. Add `data-variant="destructive"` for error states. Add a direct child `<footer>` for an inline-end action region.
```html
<div class="alert"><svg class="lucide lucide-info" 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"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
<h2>Heads up!</h2>
<section>You can add components and dependencies to your app.</section>
<footer>
<button type="button" class="btn" data-variant="outline">Enable</button>
</footer>
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="alert"&gt;</code></dt>
<dd>
Main container. Use <code>data-variant="destructive"</code> for error states.
<dl>
<dt><code>&lt;svg&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The icon.</dd>
<dt><code>&lt;h2&gt;</code></dt>
<dd>The title.</dd>
<dt><code>&lt;section&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The description.</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The action region, aligned to the inline end of the alert. Put the action element inside it, such as a button or link.</dd>
</dl>
</dd>
</dl>
## Examples
### Basic
A basic alert with an icon, title and description.
<Preview class="w-full max-w-md">
<div class="alert"><svg class="lucide lucide-circle-check" 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"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
<h2>Account updated successfully</h2>
<section>Your profile information has been saved. Changes will be reflected immediately.</section>
</div>
</Preview>
### Destructive
Use `data-variant="destructive"` to create a destructive alert.
<Preview class="w-full max-w-md">
<div class="alert" data-variant="destructive"><svg class="lucide lucide-circle-alert" 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"><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
<h2>Payment failed</h2>
<section>Your payment could not be processed. Please check your payment method and try again.</section>
</div>
</Preview>
### Action
Use `<footer>` to add a button or other action element to the alert.
<Preview class="w-full max-w-md">
<div class="alert">
<h2>Dark mode is now available</h2>
<section>Enable it under your profile settings to get started.</section>
<footer>
<button type="button" class="btn" data-size="xs">Enable</button>
</footer>
</div>
</Preview>
### Custom colors
You can customize the alert colors by adding custom classes such as `bg-amber-50 dark:bg-amber-950` to the alert.
<Preview class="w-full max-w-md">
<div class="alert border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-50"><svg class="lucide lucide-triangle-alert" 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"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
<h2>Your subscription will expire in 3 days.</h2>
<section>Renew now to avoid service interruption or upgrade to a paid plan to continue using the service.</section>
</div>
</Preview>
### RTL
Add `dir="rtl"` to the alert or an ancestor.
<Preview class="w-full max-w-md">
<div class="grid items-start gap-4" dir="rtl">
<div class="alert"><svg class="lucide lucide-circle-check" 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"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
<h2>تم الدفع بنجاح</h2>
<section>تمت معالجة دفعتك البالغة 29.99 دولارًا. تم إرسال إيصال إلى عنوان بريدك الإلكتروني.</section>
</div>
<div class="alert"><svg class="lucide lucide-info" 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"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
<h2>ميزة جديدة متاحة</h2>
<section>لقد أضفنا دعم الوضع الداكن. يمكنك تفعيله في إعدادات حسابك.</section>
</div>
</div>
</Preview>
+222
View File
@@ -0,0 +1,222 @@
# Avatar
<Preview>
<div class="flex flex-row flex-wrap items-center gap-12">
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
<span class="avatar rounded-lg">
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<span>ER</span>
</span>
<div class="avatar-group [&_.avatar]:grayscale">
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
<span class="avatar">
<img src="https://github.com/maxleiter.png" alt="@maxleiter" />
<span>LR</span>
</span>
<span class="avatar">
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<span>ER</span>
</span>
</div>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Avatar component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/avatar.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your avatar HTML">
Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-size="lg"` when needed. Put the image first and a fallback `<span>` after it. Add `.avatar-badge` for a status badge or badge icon.
```html
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="avatar-group"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>
Optional parent that groups avatars with overlap.
<dl>
<dt><code>&lt;span class="avatar"&gt;</code></dt>
<dd>
Avatar root. Use it standalone or as a direct child of <code>.avatar-group</code>. Add <code>data-size="sm"</code> or <code>data-size="lg"</code> for alternate sizes.
<dl>
<dt><code>&lt;img&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The avatar image. Provide meaningful <code>alt</code> text.</dd>
<dt><code>&lt;span&gt;</code></dt>
<dd>Fallback initials or icon.</dd>
<dt><code>&lt;span class="avatar-badge"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Status badge positioned at the block-end inline-end corner. It can be empty or contain an icon.</dd>
</dl>
</dd>
<dt><code>&lt;span data-count&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Count or icon indicator displayed at the end of the group.</dd>
</dl>
</dd>
</dl>
## Examples
### Basic
<Preview>
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
</Preview>
### Badge
<Preview>
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
<span class="avatar-badge bg-green-600 dark:bg-green-800"></span>
</span>
</Preview>
### Badge with icon
<Preview>
<span class="avatar">
<img src="https://github.com/pranathip.png" alt="@pranathip" />
<span>PP</span>
<span class="avatar-badge"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></span>
</span>
</Preview>
### Avatar group
<Preview>
<div class="avatar-group">
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
</div>
</Preview>
### Avatar group count
<Preview>
<div class="avatar-group">
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
<span data-count>+3</span>
</div>
</Preview>
### Avatar group with icon
<Preview>
<div class="avatar-group">
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
<span data-count aria-label="Additional avatars"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></span>
</div>
</Preview>
### Sizes
<Preview>
<div class="grid gap-2">
<div class="flex flex-wrap items-center gap-2">
<span class="avatar" data-size="sm">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
<span class="avatar" data-size="lg">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="avatar" data-size="sm"><span>CN</span></span>
<span class="avatar"><span>CN</span></span>
<span class="avatar" data-size="lg"><span>CN</span></span>
</div>
</div>
</Preview>
### Dropdown
<Preview>
<div class="dropdown-menu">
<button type="button" id="avatar-dropdown-trigger" aria-label="Open user menu" aria-haspopup="menu" aria-controls="avatar-dropdown-menu" aria-expanded="false" class="btn rounded-full" data-variant="ghost" data-size="icon">
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
</button>
<div data-popover aria-hidden="true" class="w-32">
<div role="menu" id="avatar-dropdown-menu" aria-labelledby="avatar-dropdown-trigger">
<div role="menuitem">Profile</div>
<div role="menuitem">Billing</div>
<div role="menuitem">Settings</div>
<hr role="separator" />
<div role="menuitem" data-variant="destructive">Log out</div>
</div>
</div>
</div>
</Preview>
### RTL
<Preview>
<div class="flex flex-row flex-wrap items-center gap-12" dir="rtl">
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
<span class="avatar rounded-lg">
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<span>ER</span>
</span>
<div class="avatar-group [&_.avatar]:grayscale">
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
</div>
</div>
</Preview>
+140
View File
@@ -0,0 +1,140 @@
# Badge
<Preview>
<div class="flex flex-wrap gap-2">
<span class="badge">Badge</span>
<span class="badge" data-variant="secondary">Secondary</span>
<span class="badge" data-variant="destructive">Destructive</span>
<span class="badge" data-variant="outline">Outline</span>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Badge component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/badge.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your badge HTML">
Add `class="badge"` to an inline element. Add `data-variant` when the badge is not the default primary variant.
Inline SVG icons are spaced automatically by the badge gap.
```html
<span class="badge">Default</span>
<span class="badge" data-variant="secondary">Secondary</span>
<span class="badge" data-variant="destructive">Destructive</span>
<span class="badge" data-variant="outline">Outline</span>
<span class="badge" data-variant="ghost">Ghost</span>
```
</Step>
</Steps>
Badges support right-to-left layouts through native text flow. Flip directional icons explicitly.
## Examples
### Variants
Use `data-variant` to change the badge variant.
<Preview>
<div class="flex flex-wrap gap-2">
<span class="badge">Default</span>
<span class="badge" data-variant="secondary">Secondary</span>
<span class="badge" data-variant="destructive">Destructive</span>
<span class="badge" data-variant="outline">Outline</span>
<span class="badge" data-variant="ghost">Ghost</span>
</div>
</Preview>
### With icon
Use `data-icon="inline-start"` or `data-icon="inline-end"` on the icon for icon-aware spacing.
<Preview>
<div class="flex flex-wrap gap-2">
<span class="badge"><svg data-icon="inline-start" class="lucide lucide-badge-check" 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"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" /><path d="m9 12 2 2 4-4" /></svg>
Verified
</span>
<span class="badge" data-variant="secondary">
Bookmark
<svg data-icon="inline-end" class="lucide lucide-bookmark" 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"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg>
</span>
</div>
</Preview>
### With spinner
Use an inline loading icon with `data-icon="inline-start"` or `data-icon="inline-end"`.
<Preview>
<div class="flex flex-wrap gap-2">
<span class="badge" data-variant="destructive"><svg aria-label="Loading" role="status" data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Deleting
</span>
<span class="badge" data-variant="secondary">
Generating
<svg aria-label="Loading" role="status" data-icon="inline-end" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
</span>
</div>
</Preview>
### Link
<Preview>
<a href="#" class="badge">
Open Link
<svg data-icon="inline-end" class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
</a>
</Preview>
### Custom colors
<Preview>
<div class="flex flex-wrap gap-2">
<span class="badge bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300">Blue</span>
<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">Green</span>
<span class="badge bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300">Sky</span>
<span class="badge bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300">Purple</span>
<span class="badge bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300">Red</span>
</div>
</Preview>
### RTL
<Preview>
<div dir="rtl" class="flex flex-wrap gap-2">
<span class="badge">شارة</span>
<span class="badge" data-variant="secondary">ثانوي</span>
<span class="badge" data-variant="destructive">مدمر</span>
<span class="badge" data-variant="outline">مخطط</span>
<span class="badge"><svg data-icon="inline-start" class="lucide lucide-badge-check" 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"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" /><path d="m9 12 2 2 4-4" /></svg>
متحقق
</span>
<span class="badge" data-variant="secondary">
إشارة مرجعية
<svg data-icon="inline-end" class="lucide lucide-bookmark" 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"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg>
</span>
</div>
</Preview>
+154
View File
@@ -0,0 +1,154 @@
# Breadcrumb
<Preview>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><a href="#">Components</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><span aria-current="page">Breadcrumb</span></li>
</ol>
</nav>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Breadcrumb component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/breadcrumb.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your breadcrumb HTML">
Use a navigation landmark with an ordered list. Mark the current page with `aria-current="page"`.
```html
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" ...></svg></li>
<li><span aria-current="page">Breadcrumb</span></li>
</ol>
</nav>
```
</Step>
</Steps>
## HTML structure
- `nav.breadcrumb` is the root landmark and should include `aria-label="Breadcrumb"`.
- `ol` contains the breadcrumb items in hierarchy order.
- `li > a` represents navigable ancestors.
- `li[aria-hidden="true"]` represents a visual separator.
- `[aria-current="page"]` represents the current page.
- `li > span[aria-hidden="true"]` represents a collapsed ellipsis.
## Examples
### Basic
<Preview>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><a href="#">Components</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><span aria-current="page">Breadcrumb</span></li>
</ol>
</nav>
</Preview>
### Custom separator
<Preview>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li aria-hidden="true"><svg class="lucide lucide-dot" 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"><circle cx="12.1" cy="12.1" r="1" /></svg></li>
<li><a href="#">Components</a></li>
<li aria-hidden="true"><svg class="lucide lucide-dot" 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"><circle cx="12.1" cy="12.1" r="1" /></svg></li>
<li><span aria-current="page">Breadcrumb</span></li>
</ol>
</nav>
</Preview>
### Dropdown
<Preview>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li>
<div id="breadcrumb-dropdown" class="dropdown-menu">
<button type="button" id="breadcrumb-dropdown-trigger" class="btn" data-variant="ghost" data-size="icon-sm" aria-haspopup="menu" aria-controls="breadcrumb-dropdown-menu" aria-expanded="false"><svg class="lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
<span class="sr-only">Toggle menu</span>
</button>
<div id="breadcrumb-dropdown-popover" data-popover aria-hidden="true">
<div role="menu" id="breadcrumb-dropdown-menu" aria-labelledby="breadcrumb-dropdown-trigger">
<nav role="menu">
<button type="button" role="menuitem">Documentation</button>
<button type="button" role="menuitem">Themes</button>
<button type="button" role="menuitem">GitHub</button>
</nav>
</div>
</div>
</div>
</li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><a href="#">Components</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><span aria-current="page">Breadcrumb</span></li>
</ol>
</nav>
</Preview>
### Collapsed
<Preview>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li> <span aria-hidden="true"><svg class="lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></span></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><a href="#">Components</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><span aria-current="page">Breadcrumb</span></li>
</ol>
</nav>
</Preview>
### RTL
<Preview>
<nav class="breadcrumb" aria-label="Breadcrumb" dir="rtl">
<ol>
<li><a href="#">الرئيسية</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><a href="#">المكونات</a></li>
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg></li>
<li><span aria-current="page">مسار التنقل</span></li>
</ol>
</nav>
</Preview>
+307
View File
@@ -0,0 +1,307 @@
# Button Group
<Preview>
<div class="flex w-fit items-stretch gap-2">
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Go Back"><svg class="lucide lucide-arrow-left" 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"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg></button>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">Archive</button>
<button type="button" class="btn" data-variant="outline">Report</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">Snooze</button>
<div id="dropdown-menu-277805" class="dropdown-menu">
<button type="button" id="dropdown-menu-277805-trigger" aria-label="More archive options" aria-haspopup="menu" aria-controls="dropdown-menu-277805-menu" aria-expanded="false" class="btn" data-variant="outline" data-size="icon"><svg class="lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></button>
<div id="dropdown-menu-277805-popover" data-popover aria-hidden="true" data-align="end">
<div role="menu" id="dropdown-menu-277805-menu" aria-labelledby="dropdown-menu-277805-trigger">
<div role="menuitem"><svg class="lucide lucide-mail-check" 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"><path d="M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /><path d="m16 19 2 2 4-4" /></svg>
Mark as Read
</div>
<div role="menuitem"><svg class="lucide lucide-archive" 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"><rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /></svg>
Archive
</div>
<hr role="separator" />
<div role="menuitem"><svg class="lucide lucide-clock" 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"><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></svg>
Snooze
</div>
<div role="menuitem"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
Add to Calendar
</div>
<div role="menuitem"><svg class="lucide lucide-list-filter-plus" 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"><path d="M12 5H2" /><path d="M6 12h12" /><path d="M9 19h6" /><path d="M16 5h6" /><path d="M19 8V2" /></svg>
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"><svg class="lucide lucide-trash-2" 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"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
Trash
</div>
</div>
</div>
</div>
</div>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, the component CSS files it composes, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/button-group.css";
@import "basecoat-css/components/button.css";
@import "basecoat-css/styles/vega.css";
```
Button Group composes Button styles for the child controls.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your button group HTML">
Wrap related controls in a `button-group`.
```html
<div role="group" aria-label="Message actions" class="button-group">
<button type="button" class="btn" data-variant="outline">Archive</button>
<button type="button" class="btn" data-variant="outline">Report</button>
</div>
```
</Step>
</Steps>
Use `data-orientation="vertical"` to stack items. Use `<hr role="separator" />` between items when you need a visible divider, such as a split action. Use direct child `<span>`, `<label>`, or `<output>` elements for non-interactive text segments. Direct child `<div>` elements are left unstyled for custom layouts.
## Examples
### Orientation
Use `data-orientation="vertical"` to stack buttons vertically.
<Preview>
<div role="group" aria-label="Media controls" class="button-group h-fit" data-orientation="vertical">
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Zoom in"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Zoom out"><svg class="lucide lucide-minus" 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"><path d="M5 12h14" /></svg></button>
</div>
</Preview>
### Size
<Preview>
<div class="flex flex-col items-start gap-8">
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline" data-size="sm">Small</button>
<button type="button" class="btn" data-variant="outline" data-size="sm">Button</button>
<button type="button" class="btn" data-variant="outline" data-size="sm">Group</button>
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Add small item"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">Default</button>
<button type="button" class="btn" data-variant="outline">Button</button>
<button type="button" class="btn" data-variant="outline">Group</button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Add item"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline" data-size="lg">Large</button>
<button type="button" class="btn" data-variant="outline" data-size="lg">Button</button>
<button type="button" class="btn" data-variant="outline" data-size="lg">Group</button>
<button type="button" class="btn" data-variant="outline" data-size="icon-lg" aria-label="Add large item"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
</div>
</Preview>
### Nested
Nest `button-group` components to keep controls joined within each group while adding spacing between groups.
<Preview>
<div role="group" aria-label="Pagination controls" class="button-group">
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline" data-size="sm">1</button>
<button type="button" class="btn" data-variant="outline" data-size="sm">2</button>
<button type="button" class="btn" data-variant="outline" data-size="sm">3</button>
<button type="button" class="btn" data-variant="outline" data-size="sm">4</button>
<button type="button" class="btn" data-variant="outline" data-size="sm">5</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Previous page"><svg class="lucide lucide-arrow-left" 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"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg></button>
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Next page"><svg class="lucide lucide-arrow-right" 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"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg></button>
</div>
</div>
</Preview>
### Separator
Use `<hr role="separator" />` to visually divide buttons within a group.
<Preview>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="secondary" data-size="sm">Copy</button>
<hr role="separator" />
<button type="button" class="btn" data-variant="secondary" data-size="sm">Paste</button>
</div>
</Preview>
### Split
Use a separator to create a split action.
<Preview>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="secondary">Button</button>
<hr role="separator" />
<button type="button" class="btn" data-variant="secondary" data-size="icon" aria-label="Add"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
</Preview>
### Input
<Preview>
<div role="group" aria-label="Search" class="button-group">
<input type="text" class="input" placeholder="Search..." />
<button type="button" class="btn" data-variant="outline" aria-label="Search"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></button>
</div>
</Preview>
### Input group
<Preview>
<div role="group" class="button-group">
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Add attachment"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
<div role="group" class="button-group">
<div class="input-group">
<input type="text" placeholder="Send a message..." />
<div role="group" data-align="inline-end">
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Voice mode" aria-pressed="false"><svg class="lucide lucide-audio-lines" 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"><path d="M2 10v3" /><path d="M6 6v11" /><path d="M10 3v18" /><path d="M14 8v7" /><path d="M18 5v13" /><path d="M22 10v3" /></svg></button>
</div>
</div>
</div>
</div>
</Preview>
### Dropdown menu
<Preview>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">Follow</button>
<div id="dropdown-menu-609880" class="dropdown-menu">
<button type="button" id="dropdown-menu-609880-trigger" aria-label="More follow options" aria-haspopup="menu" aria-controls="dropdown-menu-609880-menu" aria-expanded="false" class="btn" data-variant="outline" data-size="icon"><svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg></button>
<div id="dropdown-menu-609880-popover" data-popover aria-hidden="true" data-align="end">
<div role="menu" id="dropdown-menu-609880-menu" aria-labelledby="dropdown-menu-609880-trigger">
<div role="menuitem"><svg class="lucide lucide-volume-off" 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"><path d="M16 9a5 5 0 0 1 .95 2.293" /><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96" /><path d="m2 2 20 20" /><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11" /><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686" /></svg>
Mute Conversation
</div>
<div role="menuitem"><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg>
Mark as Read
</div>
<div role="menuitem"><svg class="lucide lucide-triangle-alert" 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"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
Report Conversation
</div>
<div role="menuitem"><svg class="lucide lucide-user-round-x" 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"><path d="M2 21a8 8 0 0 1 11.873-7" /><circle cx="10" cy="8" r="5" /><path d="m17 17 5 5" /><path d="m22 17-5 5" /></svg>
Block User
</div>
<div role="menuitem"><svg class="lucide lucide-share" 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"><path d="M12 2v13" /><path d="m16 6-4-4-4 4" /><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /></svg>
Share Conversation
</div>
<div role="menuitem"><svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
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"><svg class="lucide lucide-trash-2" 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"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
Delete Conversation
</div>
</div>
</div>
</div>
</div>
</Preview>
### Select
<Preview>
<div class="flex items-stretch gap-2">
<div role="group" aria-label="Currency amount" class="button-group">
<div id="select-797405" class="select">
<button type="button" class="[&amp;_[data-name]]:hidden" id="select-797405-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-797405-listbox">
<span class="truncate">$</span>
<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="lucide lucide-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-797405-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-797405-listbox" aria-orientation="vertical" aria-labelledby="select-797405-trigger">
<div role="option" data-value="$" data-label="$" aria-selected="true">
$
<span class="text-muted-foreground">US Dollar</span>
</div>
<div role="option" data-value="€" data-label="€">
€
<span class="text-muted-foreground">Euro</span>
</div>
<div role="option" data-value="£" data-label="£">
£
<span class="text-muted-foreground">British Pound</span>
</div>
</div>
</div>
<input type="hidden" name="select-797405-value" value="$" />
</div>
<input type="text" class="input" placeholder="10.00" />
</div>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Submit amount"><svg class="lucide lucide-arrow-right" 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"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg></button>
</div>
</Preview>
### Popover
<Preview>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline"><svg class="lucide lucide-bot" 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"><path d="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" /></svg>
Copilot
</button>
<div id="popover-788465" class="popover">
<button id="popover-788465-trigger" type="button" aria-label="Open Popover" aria-expanded="false" aria-controls="popover-788465-popover" class="btn" data-variant="outline" data-size="icon"><svg class="lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg></button>
<div id="popover-788465-popover" data-popover aria-hidden="true" class="w-72 text-sm" data-align="end">
<div>
<p class="font-medium">Start a new task with Copilot</p>
<p class="text-muted-foreground">Describe your task in natural language.</p>
</div>
<textarea class="textarea min-h-16 w-full resize-none" placeholder="I need to..."></textarea>
<p class="text-muted-foreground text-xs">Copilot will open a pull request for review.</p>
</div>
</div>
</div>
</Preview>
### RTL
Add `dir="rtl"` to the group or an ancestor. Flip directional icons explicitly.
<Preview>
<div dir="rtl">
<div role="group" aria-label="إجراءات الرسالة" class="button-group">
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="رجوع"><svg class="rtl:rotate-180 lucide lucide-arrow-left" 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"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg></button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">أرشفة</button>
<button type="button" class="btn" data-variant="outline">تقرير</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">تأجيل</button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="المزيد من الخيارات"><svg class="lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></button>
</div>
</div>
</div>
</Preview>
+243
View File
@@ -0,0 +1,243 @@
# Button
<Preview>
<div class="flex flex-wrap items-center gap-2">
<button type="button" class="btn" data-variant="outline">Button</button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Submit"><svg class="lucide lucide-arrow-up" 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"><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg></button>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Button component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/button.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your button HTML">
```html
<button type="button" class="btn">Primary</button>
<button type="button" class="btn" data-variant="outline">Outline</button>
<button type="button" class="btn" data-variant="secondary" data-size="sm">Small secondary</button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Settings"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg></button>
```
</Step>
</Steps>
Add `class="btn"` to a `<button>` or `<a>`. Add `data-variant` and `data-size` when needed. Add `data-icon="inline-start"` or `data-icon="inline-end"` to inline icons when a text button needs icon-aware spacing.
#### HTML structure
<dl>
<dt><code>&lt;button class="btn"&gt;</code></dt>
<dd>Button root. Use a native <code>&lt;button&gt;</code> for actions and <code>&lt;a&gt;</code> for navigation.</dd>
<dt><code>data-variant="primary|secondary|outline|ghost|link|destructive"</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Visual variant. Omit it for the primary variant.</dd>
<dt><code>data-size="xs|sm|default|lg|icon|icon-xs|icon-sm|icon-lg"</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Button size. Omit it for the default size.</dd>
</dl>
### Variants
| Variant | Markup |
| --- | --- |
| Primary | `<button class="btn">` |
| Secondary | `<button class="btn" data-variant="secondary">` |
| Outline | `<button class="btn" data-variant="outline">` |
| Ghost | `<button class="btn" data-variant="ghost">` |
| Link | `<button class="btn" data-variant="link">` |
| Destructive | `<button class="btn" data-variant="destructive">` |
### Sizes
| Size | Markup |
| --- | --- |
| Extra small | `<button class="btn" data-size="xs">` |
| Small | `<button class="btn" data-size="sm">` |
| Default | `<button class="btn">` |
| Large | `<button class="btn" data-size="lg">` |
| Icon extra small | `<button class="btn" data-size="icon-xs">` |
| Icon small | `<button class="btn" data-size="icon-sm">` |
| Icon | `<button class="btn" data-size="icon">` |
| Icon large | `<button class="btn" data-size="icon-lg">` |
Use `data-size="icon"` or one of the icon size variants for square buttons. Icon-only buttons need an accessible name, usually with `aria-label`.
Use `data-icon="inline-start"` or `data-icon="inline-end"` on inline icons when a text button needs icon-aware spacing. Use icon size variants only for icon-only buttons.
To group related buttons, use the [Button Group](/components/button-group/) component.
## Examples
### Size
Use `data-size` to change the size of the button.
<Preview>
<div class="flex flex-wrap items-center gap-2">
<button type="button" class="btn" data-variant="outline" data-size="xs">Extra Small</button>
<button type="button" class="btn" data-variant="outline" data-size="icon-xs" aria-label="Submit"><svg class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg></button>
<button type="button" class="btn" data-variant="outline" data-size="sm">Small</button>
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Submit"><svg class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg></button>
<button type="button" class="btn" data-variant="outline">Default</button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Submit"><svg class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg></button>
<button type="button" class="btn" data-variant="outline" data-size="lg">Large</button>
<button type="button" class="btn" data-variant="outline" data-size="icon-lg" aria-label="Submit"><svg class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg></button>
</div>
</Preview>
### Default
<Preview>
<button type="button" class="btn">Button</button>
</Preview>
### Outline
<Preview>
<button type="button" class="btn" data-variant="outline">Outline</button>
</Preview>
### Secondary
<Preview>
<button type="button" class="btn" data-variant="secondary">Secondary</button>
</Preview>
### Ghost
<Preview>
<button type="button" class="btn" data-variant="ghost">Ghost</button>
</Preview>
### Destructive
<Preview>
<button type="button" class="btn" data-variant="destructive">Destructive</button>
</Preview>
### Link
<Preview>
<button type="button" class="btn" data-variant="link">Link</button>
</Preview>
### Icon
<Preview>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Open"><svg class="lucide lucide-circle-fading-arrow-up" 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"><path d="M12 2a10 10 0 0 1 7.38 16.75" /><path d="m16 12-4-4-4 4" /><path d="M12 16V8" /><path d="M2.5 8.875a10 10 0 0 0-.5 3" /><path d="M2.83 16a10 10 0 0 0 2.43 3.4" /><path d="M4.636 5.235a10 10 0 0 1 .891-.857" /><path d="M8.644 21.42a10 10 0 0 0 7.631-.38" /></svg></button>
</Preview>
### With icon
Use `data-icon="inline-start"` or `data-icon="inline-end"` on inline icons to match upstream icon-aware spacing.
<Preview>
<button type="button" class="btn" data-variant="outline" data-size="sm"><svg data-icon="inline-start" class="lucide lucide-git-branch" 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"><path d="M15 6a9 9 0 0 0-9 9V3" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /></svg>
New Branch
</button>
</Preview>
### Rounded
Use the `rounded-full` class to make the button rounded.
<Preview>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon" aria-label="Scroll up"><svg class="lucide lucide-arrow-up" 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"><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg></button>
</Preview>
### Spinner
<Preview>
<div class="flex flex-wrap items-center gap-2">
<button type="button" class="btn" disabled><svg data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Generating
</button>
<button type="button" class="btn" data-variant="secondary" disabled>
Downloading
<svg data-icon="inline-end" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
</button>
</div>
</Preview>
### Button group
To group related buttons, use the [Button Group](/components/button-group/) component.
<Preview>
<div class="flex w-fit items-stretch gap-2">
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Go back"><svg class="lucide lucide-arrow-left" 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"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg></button>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">Archive</button>
<button type="button" class="btn" data-variant="outline">Report</button>
</div>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="outline">Snooze</button>
<div id="button-docs-actions" class="dropdown-menu">
<button type="button" id="button-docs-actions-trigger" aria-label="More options" aria-haspopup="menu" aria-controls="button-docs-actions-menu" aria-expanded="false" class="btn" data-variant="outline" data-size="icon"><svg class="lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></button>
<div id="button-docs-actions-popover" data-popover aria-hidden="true" data-align="end">
<div role="menu" id="button-docs-actions-menu" aria-labelledby="button-docs-actions-trigger">
<div role="menuitem"><svg class="lucide lucide-mail-check" 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"><path d="M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /><path d="m16 19 2 2 4-4" /></svg>
Mark as Read
</div>
<div role="menuitem"><svg class="lucide lucide-archive" 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"><rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /></svg>
Archive
</div>
<hr role="separator" />
<div role="menuitem"><svg class="lucide lucide-clock" 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"><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></svg>
Snooze
</div>
<div role="menuitem"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
Add to Calendar
</div>
<div role="menuitem"><svg class="lucide lucide-list-filter-plus" 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"><path d="M12 5H2" /><path d="M6 12h12" /><path d="M9 19h6" /><path d="M16 5h6" /><path d="M19 8V2" /></svg>
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"><svg class="lucide lucide-trash-2" 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"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
Trash
</div>
</div>
</div>
</div>
</div>
</div>
</Preview>
### RTL
Add `dir="rtl"` to the button or an ancestor. Flip directional icons explicitly.
<Preview>
<div dir="rtl" class="flex flex-wrap items-center gap-2">
<button type="button" class="btn" data-variant="outline">زر</button>
<button type="button" class="btn" data-variant="destructive">حذف</button>
<button type="button" class="btn" data-variant="outline">
إرسال
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" 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"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
</button>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Add"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
<button type="button" class="btn" data-variant="secondary" disabled><svg data-icon="inline-start" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
جاري التحميل
</button>
</div>
</Preview>
+180
View File
@@ -0,0 +1,180 @@
# Card
<Preview class="w-full max-w-sm">
<div class="card">
<header>
<h2>Login to your account</h2>
<p>Enter your email below to login to your account</p>
<div class="card-action">
<button type="button" class="btn" data-variant="link">Sign Up</button>
</div>
</header>
<section>
<form class="grid gap-6">
<div class="grid gap-2">
<label class="label" for="demo-card-email">Email</label>
<input class="input" type="email" id="demo-card-email" placeholder="m@example.com" required />
</div>
<div class="grid gap-2">
<div class="flex items-center">
<label class="label" for="demo-card-password">Password</label>
<a href="#" class="ms-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
</div>
<input class="input" type="password" id="demo-card-password" required />
</div>
</form>
</section>
<footer class="flex-col gap-2">
<button type="button" class="btn w-full">Login</button>
<button type="button" class="btn w-full" data-variant="outline">Login with Google</button>
</footer>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Card component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/card.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your card HTML">
```html
<div class="card">
<header>
<h2>Card Title</h2>
<p>Card Description</p>
<div class="card-action">Card Action</div>
</header>
<section> <p>Card Content</p></section>
<footer>
<p>Card Footer</p>
</footer>
</div>
```
</Step>
</Steps>
## HTML structure
<dl>
<dt><code>&lt;div class="card"&gt;</code></dt>
<dd>
The card container. Use <code>data-size="sm"</code> for a denser layout.
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>
The card header.
<dl>
<dt><code>&lt;h2&gt;</code> or <code>&lt;h3&gt;</code></dt>
<dd>The card title.</dd>
<dt><code>&lt;p&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The card description.</dd>
<dt><code>&lt;div class="card-action"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Action or metadata placed in the top-right of the header. Use <code>&lt;menu class="card-action"&gt;</code> when the content is a list of commands.</dd>
</dl>
</dd>
<dt><code>&lt;section&gt;</code></dt>
<dd>The main card content.</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Footer actions or secondary content at the bottom of the card.</dd>
</dl>
</dd>
</dl>
## Examples
### Size
Use `data-size="sm"` to set the size of the card to small. The small size variant uses smaller spacing.
<Preview class="w-full max-w-sm">
<div class="card" data-size="sm">
<header>
<h2>Small Card</h2>
<p>This card uses the small size variant.</p>
<div class="card-action">
<button type="button" class="btn" data-size="sm" data-variant="outline">Action</button>
</div>
</header>
<section> <p>The card component supports a size attribute that can be set to <code>sm</code> for a more compact appearance.</p></section>
</div>
</Preview>
### Image
Add an image before the card header to create a card with an image.
<Preview class="w-full max-w-sm">
<div class="card">
<img
alt="Event cover"
class="aspect-video w-full object-cover"
src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=1200&auto=format&fit=crop&q=80"
/>
<header>
<h2>Design systems meetup</h2>
<p>A practical talk on component APIs, accessibility, and shipping faster.</p>
<div class="card-action">
<span class="badge" data-variant="secondary">Featured</span>
</div>
</header>
<footer>
<button type="button" class="btn w-full">View Event</button>
</footer>
</div>
</Preview>
## RTL
Add `dir="rtl"` to the card or an ancestor.
<Preview class="w-full max-w-sm">
<div class="card" dir="rtl">
<header>
<h2>تسجيل الدخول إلى حسابك</h2>
<p>أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك</p>
<div class="card-action">
<button type="button" class="btn" data-variant="link">إنشاء حساب</button>
</div>
</header>
<section>
<form class="grid gap-6">
<div class="grid gap-2">
<label class="label" for="demo-card-rtl-email">البريد الإلكتروني</label>
<input class="input" type="email" id="demo-card-rtl-email" placeholder="m@example.com" required />
</div>
<div class="grid gap-2">
<div class="flex items-center">
<label class="label" for="demo-card-rtl-password">كلمة المرور</label>
<a href="#" class="ms-auto inline-block text-sm underline-offset-4 hover:underline">نسيت كلمة المرور؟</a>
</div>
<input class="input" type="password" id="demo-card-rtl-password" required />
</div>
</form>
</section>
<footer class="flex-col gap-2">
<button type="button" class="btn w-full">تسجيل الدخول</button>
<button type="button" class="btn w-full" data-variant="outline">تسجيل الدخول باستخدام Google</button>
</footer>
</div>
</Preview>
+268
View File
@@ -0,0 +1,268 @@
import basecoatRuntimeSource from "../../../src/js/basecoat.js?raw"
import chartRuntimeSource from "../../../src/js/chart.js?raw"
import chartExamplesSource from "../site/chart-examples.js?raw"
# Chart
<Callout type="warning" title="Beta API" icon="triangle-alert">
Chart support is beta. The JavaScript API, generated markup, and option names may change before the stable 1.0 release.
</Callout>
Use Chart.js with Basecoat defaults for themed colors, external tooltips, and optional generated legends.
<Preview class="w-full">
<canvas id="chart-example-overview" aria-label="Visitors by device"></canvas>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Chart component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/chart.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve Chart.js, the Basecoat runtime, and Chart script.
```html
<script src="/assets/js/chart.umd.min.js"></script>
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/chart.min.js" defer></script>
```
Chart is not included in the full Basecoat JavaScript bundle.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your chart HTML">
```html
<canvas id="visitors-chart" aria-label="Monthly visitors"></canvas>
<script>
document.addEventListener("DOMContentLoaded", () => {
window.basecoat.chart("#visitors-chart", {
type: "bar",
labelKey: "month",
data: [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
{ month: "Mar", desktop: 237, mobile: 120 },
{ month: "Apr", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "Jun", desktop: 214, mobile: 140 },
],
series: {
desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)" },
},
})
})
</script>
```
</Step>
</Steps>
`basecoat.chart()` targets the `<canvas>` directly and creates Basecoat's internal `.chart` container for Chart.js sizing. You do not need to add a wrapper or class in your HTML.
Charts default to `aspect-video`. Use a height, `min-h-*`, or `aspect-*` utility on a parent element when you need a different measuring box.
## Legend
Set `legend: true` to generate a Basecoat-styled legend after the canvas.
The generated legend is display-only by default, matching shadcn/ui. Use a Chart.js plugin or custom legend markup if you need click-to-toggle behavior.
```js
window.basecoat.chart("#visitors-chart", {
type: "bar",
labelKey: "month",
data,
series,
legend: true,
})
```
## Series
`series` maps object keys from each data row to labels, colors, and optional Chart.js dataset settings.
```js
window.basecoat.chart("#revenue-chart", {
type: "line",
labelKey: "month",
data,
series: {
recurring: {
label: "Recurring",
color: "var(--chart-1)",
surface: "gradient",
dataset: {
fill: true,
tension: 0.35,
},
},
new: {
label: "New",
color: "var(--chart-2)",
dataset: {
borderDash: [4, 4],
},
},
},
})
```
## Escape Hatches
Pass raw Chart.js options and plugins through `options` and `plugins`.
```js
const chart = window.basecoat.chart("#visitors-chart", {
type: "bar",
labelKey: "month",
data,
series,
options: {
indexAxis: "x",
scales: {
y: {
beginAtZero: true,
},
},
plugins: {
tooltip: {
mode: "index",
},
},
},
plugins: [myChartJsPlugin],
})
```
Pass complete Chart.js data through `chartData` when the simplified `data` plus `series` mapper is not enough.
```js
window.basecoat.chart("#custom-chart", {
type: "scatter",
chartData: {
datasets: [
{
label: "Samples",
data: [{ x: 1, y: 2 }, { x: 2, y: 5 }],
},
],
},
options: {
scales: {
x: { type: "linear" },
},
},
})
```
`basecoat.chart()` returns the Chart.js instance for single targets and an array of instances for multi-target selectors.
## Examples
### Bar chart - multiple
<Preview class="w-full">
<canvas id="chart-example-visitors" aria-label="Monthly visitors by device"></canvas>
</Preview>
### Line chart - linear
<Preview class="w-full">
<canvas id="chart-example-line" aria-label="Revenue line chart"></canvas>
</Preview>
### Line chart - step
<Preview class="w-full">
<canvas id="chart-example-step" aria-label="Support queue step chart"></canvas>
</Preview>
### Bar chart - stacked
<Preview class="w-full">
<canvas id="chart-example-stacked" aria-label="Stacked pipeline bar chart"></canvas>
</Preview>
### Pie chart - donut
<Preview class="w-full">
<canvas id="chart-example-donut" aria-label="Traffic source donut chart"></canvas>
</Preview>
### Radar chart
<Preview class="w-full">
<canvas id="chart-example-radar" aria-label="Channel performance radar chart"></canvas>
</Preview>
<script is:inline src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
<script is:inline>{`
(() => {
const initChartPage = () => {
if (!window.Chart) {
requestAnimationFrame(initChartPage)
return
}
if (!window.basecoat) {
${basecoatRuntimeSource}
}
if (!window.basecoat.chart) {
${chartRuntimeSource}
}
${chartExamplesSource}
}
initChartPage()
})()
`}</script>
## API
| Option | Default | Description |
| --- | --- | --- |
| `type` | `"bar"` | Chart.js chart type. |
| `labelKey` | `"label"` | Row key used for labels. |
| `data` | `[]` | Array of row objects, or raw Chart.js data with `datasets`. |
| `series` | `{}` | Series config mapped from row keys. |
| `legend` | `false` | Generates a Basecoat legend after the canvas. |
| `tooltip` | `true` | Uses the Basecoat external tooltip. |
| `options` | `{}` | Raw Chart.js options. |
| `plugins` | `[]` | Raw Chart.js plugins. |
| `chartData` | `undefined` | Complete Chart.js data object. Overrides the data mapper. |
### Series options
| Option | Description |
| --- | --- |
| `label` | Display label for legends and tooltips. |
| `color` | Base color for the series stroke, fill, legend marker, and tooltip marker. |
| `surface` | Derives a fill from `color`. Use `true` for a translucent fill, `"gradient"` for an upstream-style vertical fade, or `{ from, to }` for custom opacity stops. |
| `dataset` | Raw Chart.js dataset options. |
+227
View File
@@ -0,0 +1,227 @@
# Checkbox
<Preview>
<div role="group" class="fieldset">
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms-checkbox" name="terms-checkbox" class="input" />
<label for="terms-checkbox">Accept terms and conditions</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms-checkbox-2" name="terms-checkbox-2" class="input" checked />
<section>
<label for="terms-checkbox-2">Accept terms and conditions</label>
<p>By clicking this checkbox, you agree to the terms.</p>
</section>
</div>
<div role="group" class="field" data-orientation="horizontal" data-disabled>
<input type="checkbox" id="toggle-checkbox" name="toggle-checkbox" class="input" disabled />
<label for="toggle-checkbox">Enable notifications</label>
</div>
<label>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="toggle-checkbox-2" name="toggle-checkbox-2" class="input" />
<section>
<h3>Enable notifications</h3>
<p>You can enable or disable notifications at any time.</p>
</section>
</div>
</label>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Checkbox component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/checkbox.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your checkbox HTML">
Add `class="input"` to checkbox inputs. Pair it with [Field](/components/field) and a native `<label>` for layout and labeling.
```html
<input type="checkbox" class="input" />
```
</Step>
</Steps>
## Examples
### Basic
Pair the checkbox with `field` and a native `<label>` for proper layout and labeling.
<Preview>
<div role="group" class="fieldset">
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms-checkbox-basic" name="terms-checkbox-basic" class="input" />
<label for="terms-checkbox-basic">Accept terms and conditions</label>
</div>
</div>
</Preview>
### Description
Use a `section` and paragraph for helper text.
<Preview>
<div role="group" class="fieldset">
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms-checkbox-desc" name="terms-checkbox-desc" class="input" checked aria-describedby="terms-checkbox-desc-description" />
<section>
<label for="terms-checkbox-desc">Accept terms and conditions</label>
<p id="terms-checkbox-desc-description">By clicking this checkbox, you agree to the terms and conditions.</p>
</section>
</div>
</div>
</Preview>
### Disabled
Use the `disabled` attribute to prevent interaction and add `data-disabled` to the `.field` component for disabled styles.
<Preview>
<div role="group" class="fieldset">
<div role="group" class="field" data-orientation="horizontal" data-disabled>
<input type="checkbox" id="toggle-checkbox-disabled" name="toggle-checkbox-disabled" class="input" disabled />
<label for="toggle-checkbox-disabled">Enable notifications</label>
</div>
</div>
</Preview>
### Invalid
Set `aria-invalid="true"` on the checkbox and `data-invalid` on the field wrapper to show invalid styles.
<Preview>
<div role="group" class="fieldset">
<div role="group" class="field" data-orientation="horizontal" data-invalid>
<input type="checkbox" id="terms-checkbox-invalid" name="terms-checkbox-invalid" class="input" aria-invalid="true" />
<label for="terms-checkbox-invalid">Accept terms and conditions</label>
</div>
</div>
</Preview>
### Group
Use multiple fields to create a checkbox list.
<Preview>
<fieldset class="fieldset">
<legend data-variant="label">Show these items on the desktop:</legend>
<p>Select the items you want to show on the desktop.</p>
<div role="group" aria-label="Desktop items" data-slot="checkbox-group">
<div role="group" class="field" data-orientation="horizontal">
<input class="input" type="checkbox" id="finder-pref-9k2-hard-disks-ljj-checkbox" name="finder-pref-9k2-hard-disks-ljj-checkbox" checked />
<label for="finder-pref-9k2-hard-disks-ljj-checkbox" class="font-normal">Hard disks</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input class="input" type="checkbox" id="finder-pref-9k2-external-disks-1yg-checkbox" name="finder-pref-9k2-external-disks-1yg-checkbox" checked />
<label for="finder-pref-9k2-external-disks-1yg-checkbox" class="font-normal">External disks</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input class="input" type="checkbox" id="finder-pref-9k2-cds-dvds-fzt-checkbox" name="finder-pref-9k2-cds-dvds-fzt-checkbox" />
<label for="finder-pref-9k2-cds-dvds-fzt-checkbox" class="font-normal">CDs, DVDs, and iPods</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input class="input" type="checkbox" id="finder-pref-9k2-connected-servers-6l2-checkbox" name="finder-pref-9k2-connected-servers-6l2-checkbox" />
<label for="finder-pref-9k2-connected-servers-6l2-checkbox" class="font-normal">Connected servers</label>
</div>
</div>
</fieldset>
</Preview>
### Table
<Preview class="w-full">
<div class="table-container">
<table class="table" data-checkbox-table>
<thead>
<tr>
<th class="w-8"><input type="checkbox" id="select-all-checkbox" name="select-all-checkbox" class="input" aria-label="Select all" /></th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr data-state="selected">
<td><input type="checkbox" id="row-1-checkbox" name="row-1-checkbox" class="input" aria-label="Select Sarah Chen" checked /></td>
<td class="font-medium">Sarah Chen</td>
<td>sarah.chen@example.com</td>
<td>Admin</td>
</tr>
<tr>
<td><input type="checkbox" id="row-2-checkbox" name="row-2-checkbox" class="input" aria-label="Select Marcus Rodriguez" /></td>
<td class="font-medium">Marcus Rodriguez</td>
<td>marcus.rodriguez@example.com</td>
<td>User</td>
</tr>
<tr>
<td><input type="checkbox" id="row-3-checkbox" name="row-3-checkbox" class="input" aria-label="Select Priya Patel" /></td>
<td class="font-medium">Priya Patel</td>
<td>priya.patel@example.com</td>
<td>User</td>
</tr>
<tr>
<td><input type="checkbox" id="row-4-checkbox" name="row-4-checkbox" class="input" aria-label="Select David Kim" /></td>
<td class="font-medium">David Kim</td>
<td>david.kim@example.com</td>
<td>Editor</td>
</tr>
</tbody>
</table>
</div>
</Preview>
## RTL
Add `dir="rtl"` to the field group or an ancestor.
<Preview>
<div role="group" class="fieldset" dir="rtl">
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms-checkbox-rtl" name="terms-checkbox" class="input" />
<label for="terms-checkbox-rtl">قبول الشروط والأحكام</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms-checkbox-2-rtl" name="terms-checkbox-2" class="input" checked aria-describedby="terms-checkbox-2-rtl-description" />
<section>
<label for="terms-checkbox-2-rtl">قبول الشروط والأحكام</label>
<p id="terms-checkbox-2-rtl-description">بالنقر على هذا المربع، فإنك توافق على الشروط.</p>
</section>
</div>
<div role="group" class="field" data-orientation="horizontal" data-disabled>
<input type="checkbox" id="toggle-checkbox-rtl" name="toggle-checkbox" class="input" disabled />
<label for="toggle-checkbox-rtl">تفعيل الإشعارات</label>
</div>
<label>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="toggle-checkbox-2-rtl" name="toggle-checkbox-2" class="input" />
<section>
<h3>تفعيل الإشعارات</h3>
<p>يمكنك تفعيل أو إلغاء تفعيل الإشعارات في أي وقت.</p>
</section>
</div>
</label>
</div>
</Preview>
+353
View File
@@ -0,0 +1,353 @@
# Combobox
<Preview>
<div id="framework-combobox" class="combobox">
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="framework-combobox-listbox" /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="framework-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="framework-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="framework" value="" />
</div>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#combobox" }}>
This component ships a `combobox()` macro for Jinja and Nunjucks.
</Callout>
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Combobox component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/combobox.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Combobox script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/combobox.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your combobox HTML">
Combobox is input-first. The visible input filters the list; the hidden input stores the submitted value.
```html
<div id="framework-combobox" class="combobox w-[240px]">
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="framework-combobox-listbox" /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="framework-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="framework-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="framework" value="" />
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="combobox"&gt;</code></dt>
<dd>
Root element. Add <code>data-auto-highlight="true"</code> to make the first visible option active when the list opens or filters. Add <code>data-format="object"</code> to serialize selected values as objects. Add <code>data-filter="manual"</code> when your app owns filtering.
<dl>
<dt><code>&lt;input type="text" role="combobox"&gt;</code></dt>
<dd>The editable control. It can be a direct child, inside <code>.input-group</code>, or inside <code>data-popover</code> for popup mode.</dd>
<dt><code>&lt;button aria-haspopup="listbox"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Opens the list from an input group or popup trigger. In popup mode, include a child with <code>data-value</code> and optional <code>data-placeholder</code> to display the selected value.</dd>
<dt><code>&lt;button data-clear&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Clears the current selection. Put the clear icon inside the button.</dd>
<dt><code>&lt;svg class="combobox-trigger-icon"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Decorative trailing icon for input-style comboboxes. Replace the SVG to customize the icon.</dd>
<dt><code>&lt;div data-popover&gt;</code></dt>
<dd>The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.</dd>
<dt><code>&lt;div role="listbox"&gt;</code></dt>
<dd>The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode. The component uses that attribute to render selected chips around the input.</dd>
<dt><code>&lt;div role="option" data-value="..."&gt;</code></dt>
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the input should display different text than the rendered content.</dd>
<dt><code>&lt;input type="hidden"&gt;</code></dt>
<dd>Submitted value. Single mode stores a string; multiple mode stores a JSON array. With <code>data-format="object"</code>, selected values are stored as objects.</dd>
</dl>
</dd>
</dl>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `change` | Event | Dispatched on selection changes with `event.detail.value` and `event.detail.selected`. |
| `combobox.setValue(value)` | Method | Sets the selected value from a plain value or `{ value, label }` object. |
| `combobox.clear()` | Method | Clears the selected value. |
| `combobox.refresh()` | Method | Rescans options after children change inside the existing `role="listbox"` element. |
## Examples
### Multiple
<Preview class="w-full max-w-xs">
<div id="frameworks-combobox" class="combobox w-full" data-auto-highlight="true">
<input type="text" role="combobox" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="frameworks-combobox-listbox" />
<div id="frameworks-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="frameworks-combobox-listbox" aria-orientation="vertical" aria-multiselectable="true" data-empty="No items found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="frameworks" value='["Next.js"]' />
</div>
</Preview>
### Clear button
<Preview>
<div id="clear-combobox" class="combobox">
<div class="input-group">
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="clear-combobox-listbox" />
<span role="group" data-align="inline-end">
<button type="button" data-clear aria-label="Clear selection"><svg class="size-4" aria-hidden="true" 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"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Open suggestions" aria-haspopup="listbox" aria-expanded="false" aria-controls="clear-combobox-listbox"><svg aria-hidden="true" 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"><path d="m6 9 6 6 6-6" /></svg></button>
</span>
</div>
<div id="clear-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="clear-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" name="framework-clear" value="Next.js" />
</div>
</Preview>
### Groups
<Preview>
<div id="timezone-combobox" class="combobox">
<input type="text" role="combobox" placeholder="Select a timezone" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="timezone-combobox-listbox" /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="timezone-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="timezone-combobox-listbox" class="scrollbar-sm" aria-orientation="vertical" data-empty="No timezones found.">
<div role="group" aria-labelledby="timezone-americas">
<div role="heading" id="timezone-americas">Americas</div>
<div role="option" data-value="(GMT-5) New York">(GMT-5) New York</div>
<div role="option" data-value="(GMT-8) Los Angeles">(GMT-8) Los Angeles</div>
<div role="option" data-value="(GMT-6) Chicago">(GMT-6) Chicago</div>
<div role="option" data-value="(GMT-5) Toronto">(GMT-5) Toronto</div>
<div role="option" data-value="(GMT-8) Vancouver">(GMT-8) Vancouver</div>
<div role="option" data-value="(GMT-3) São Paulo">(GMT-3) São Paulo</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="timezone-europe">
<div role="heading" id="timezone-europe">Europe</div>
<div role="option" data-value="(GMT+0) London">(GMT+0) London</div>
<div role="option" data-value="(GMT+1) Paris">(GMT+1) Paris</div>
<div role="option" data-value="(GMT+1) Berlin">(GMT+1) Berlin</div>
<div role="option" data-value="(GMT+1) Rome">(GMT+1) Rome</div>
<div role="option" data-value="(GMT+1) Madrid">(GMT+1) Madrid</div>
<div role="option" data-value="(GMT+1) Amsterdam">(GMT+1) Amsterdam</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="timezone-asia-pacific">
<div role="heading" id="timezone-asia-pacific">Asia/Pacific</div>
<div role="option" data-value="(GMT+9) Tokyo">(GMT+9) Tokyo</div>
<div role="option" data-value="(GMT+8) Shanghai">(GMT+8) Shanghai</div>
<div role="option" data-value="(GMT+8) Singapore">(GMT+8) Singapore</div>
<div role="option" data-value="(GMT+4) Dubai">(GMT+4) Dubai</div>
<div role="option" data-value="(GMT+11) Sydney">(GMT+11) Sydney</div>
<div role="option" data-value="(GMT+9) Seoul">(GMT+9) Seoul</div>
</div>
</div>
</div>
<input type="hidden" value="" />
</div>
</Preview>
### Custom items
<Preview>
<div id="country-combobox" class="combobox">
<input type="text" role="combobox" placeholder="Search countries" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="country-combobox-listbox" /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="country-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="country-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="argentina" data-label="Argentina" data-filter="Argentina South America"><span class="flex flex-col"><span>Argentina</span><span class="text-muted-foreground text-xs">South America</span></span></div>
<div role="option" data-value="australia" data-label="Australia" data-filter="Australia Oceania"><span class="flex flex-col"><span>Australia</span><span class="text-muted-foreground text-xs">Oceania</span></span></div>
<div role="option" data-value="japan" data-label="Japan" data-filter="Japan Asia"><span class="flex flex-col"><span>Japan</span><span class="text-muted-foreground text-xs">Asia</span></span></div>
</div>
</div>
<input type="hidden" value="" />
</div>
</Preview>
### Invalid
<Preview>
<div role="group" class="field" data-invalid="true">
<label for="invalid-combobox-input">Framework</label>
<div id="invalid-combobox" class="combobox">
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="invalid-combobox-listbox" id="invalid-combobox-input" aria-invalid="true" /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="invalid-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="invalid-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" value="" />
</div>
<p role="alert">Select a framework to continue.</p>
</div>
</Preview>
### Disabled
<Preview>
<div id="disabled-combobox" class="combobox">
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="disabled-combobox-listbox" disabled /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="disabled-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="disabled-combobox-listbox" aria-orientation="vertical">
<div role="option" data-value="Next.js">Next.js</div>
</div>
</div>
<input type="hidden" value="" />
</div>
</Preview>
### Auto highlight
Auto highlight makes the first visible option active when the list opens or filters, so pressing Enter can select it without arrowing first.
<Preview>
<div id="auto-highlight-combobox" class="combobox" data-auto-highlight="true">
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="auto-highlight-combobox-listbox" /><svg aria-hidden="true" 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="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
<div id="auto-highlight-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="auto-highlight-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="Next.js">Next.js</div>
<div role="option" data-value="SvelteKit">SvelteKit</div>
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
<div role="option" data-value="Remix">Remix</div>
<div role="option" data-value="Astro">Astro</div>
</div>
</div>
<input type="hidden" value="" />
</div>
</Preview>
### Popup
<Preview>
<div id="popup-combobox" class="combobox">
<button type="button" class="btn w-64 justify-between font-normal" data-variant="outline" aria-haspopup="listbox" aria-expanded="false" aria-controls="popup-combobox-listbox">
<span data-value data-placeholder="Select country">Select country</span>
<svg aria-hidden="true" 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"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="popup-combobox-popover" data-popover aria-hidden="true">
<div class="input-group">
<input type="text" role="combobox" placeholder="Search" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="popup-combobox-listbox" />
</div>
<div role="listbox" id="popup-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
<div role="option" data-value="argentina" data-label="Argentina">Argentina</div>
<div role="option" data-value="australia" data-label="Australia">Australia</div>
<div role="option" data-value="brazil" data-label="Brazil">Brazil</div>
<div role="option" data-value="canada" data-label="Canada">Canada</div>
<div role="option" data-value="japan" data-label="Japan">Japan</div>
<div role="option" data-value="united-states" data-label="United States">United States</div>
</div>
</div>
<input type="hidden" value="" />
</div>
</Preview>
### Input group
<Preview>
<div id="input-group-combobox" class="combobox w-60">
<div class="input-group">
<span data-align="inline-start"><svg aria-hidden="true" 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"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg></span>
<input type="text" role="combobox" placeholder="Select a timezone" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="input-group-combobox-listbox" />
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" data-align="inline-end" aria-label="Open suggestions" aria-haspopup="listbox" aria-expanded="false" aria-controls="input-group-combobox-listbox"><svg aria-hidden="true" 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"><path d="m6 9 6 6 6-6" /></svg></button>
</div>
<div id="input-group-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="input-group-combobox-listbox" aria-orientation="vertical" data-empty="No timezones found.">
<div role="group" aria-labelledby="input-group-americas">
<div role="heading" id="input-group-americas">Americas</div>
<div role="option" data-value="(GMT-5) New York">(GMT-5) New York</div>
<div role="option" data-value="(GMT-8) Los Angeles">(GMT-8) Los Angeles</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="input-group-europe">
<div role="heading" id="input-group-europe">Europe</div>
<div role="option" data-value="(GMT+0) London">(GMT+0) London</div>
<div role="option" data-value="(GMT+1) Paris">(GMT+1) Paris</div>
</div>
</div>
</div>
<input type="hidden" value="" />
</div>
</Preview>
### RTL
<Preview>
<div dir="rtl" class="field w-full max-w-xs">
<label for="rtl-combobox-input">الفئات</label>
<div id="rtl-combobox" class="combobox w-full" data-auto-highlight="true">
<input id="rtl-combobox-input" type="text" role="combobox" placeholder="أضف فئات" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="rtl-combobox-listbox" />
<div id="rtl-combobox-popover" data-popover aria-hidden="true">
<div role="listbox" id="rtl-combobox-listbox" aria-orientation="vertical" aria-multiselectable="true" data-empty="لم يتم العثور على فئات.">
<div role="option" data-value="technology" data-label="التكنولوجيا">التكنولوجيا</div>
<div role="option" data-value="design" data-label="التصميم">التصميم</div>
<div role="option" data-value="business" data-label="الأعمال">الأعمال</div>
<div role="option" data-value="marketing" data-label="التسويق">التسويق</div>
</div>
</div>
<input type="hidden" value='["technology"]' />
</div>
</div>
</Preview>
+390
View File
@@ -0,0 +1,390 @@
# Command
<Preview class="w-full max-w-sm">
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
</header>
<div role="menu" id="demo-command-standalone-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="suggestions">
<span role="heading" id="suggestions">Suggestions</span>
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
<span>Calendar</span>
</div>
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction"><svg class="lucide lucide-smile" 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"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
<span>Search Emoji</span>
</div>
<div role="menuitem" aria-disabled="true"><svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
<span>Calculator</span>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="settings">
<span role="heading" id="settings">Settings</span>
<div role="menuitem" data-filter="Profile" data-keywords="user account"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
<span>Profile</span>
<span data-shortcut>⌘P</span>
</div>
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
<span>Billing</span>
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem" data-filter="Settings" data-keywords="config preferences"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
<span>Settings</span>
<span data-shortcut>⌘S</span>
</div>
</div>
</div>
</div>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#command" }}>
This component ships `command()` and `command_dialog()` macros for Jinja and Nunjucks.
</Callout>
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Command component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/command.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Command script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/command.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your command HTML">
```html
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
</header>
<div role="menu" id="demo-command-standalone-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="suggestions">
<span role="heading" id="suggestions">Suggestions</span>
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
<span>Calendar</span>
</div>
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction"><svg class="lucide lucide-smile" 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"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
<span>Search Emoji</span>
</div>
<div role="menuitem" aria-disabled="true"><svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
<span>Calculator</span>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="settings">
<span role="heading" id="settings">Settings</span>
<div role="menuitem" data-filter="Profile" data-keywords="user account"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
<span>Profile</span>
<span data-shortcut>⌘P</span>
</div>
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
<span>Billing</span>
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem" data-filter="Settings" data-keywords="config preferences"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
<span>Settings</span>
<span data-shortcut>⌘S</span>
</div>
</div>
</div>
</div>
```
Use `<dialog class="command-dialog">` for the command palette variant.
```html
<button class="btn" data-variant="outline" onclick="document.getElementById('command-palette').showModal()">Open</button>
<dialog id="command-palette" class="command-dialog" aria-label="Command menu">
<div class="command">
<!-- Command content -->
</div>
</dialog>
```
</Step>
</Steps>
### HTML structure
The command menu can be used standalone or inside a native dialog. The structure is:
<dl>
<dt><code>&lt;dialog class="command-dialog"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>
Dialog variant. Wraps one direct child <code>&lt;div class="command"&gt;</code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
<dl>
<dt><code>&lt;div class="command"&gt;</code></dt>
<dd>
Root command menu. Can also be used standalone without the dialog wrapper. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote search or a local Lunr index.
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>
Search input wrapper. The macro includes a search icon and input.
<dl>
<dt><code>&lt;input type="text" role="combobox"&gt;</code></dt>
<dd>Filter input. Use <code>aria-expanded="true"</code> and <code>aria-controls="&#123; MENU_ID &#125;"</code> to point to the command list.</dd>
</dl>
</dd>
<dt><code>&lt;div role="menu" id="&#123; MENU_ID &#125;"&gt;</code></dt>
<dd>
Command list. Set <code>data-empty</code> to customize the empty state.
<dl>
<dt><code>&lt;div role="group" aria-labelledby="&#123; HEADING_ID &#125;"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
<dt><code>&lt;span role="heading" id="&#123; HEADING_ID &#125;"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
<dt><code>&lt;div role="menuitem"&gt;</code> or <code>&lt;a role="menuitem"&gt;</code></dt>
<dd>Selectable command item. Use <code>&lt;a&gt;</code> for navigation and <code>&lt;div&gt;</code> for actions. Supports <code>data-filter</code>, <code>data-keywords</code>, <code>data-force</code>, <code>data-keep-command-open</code>, <code>aria-disabled="true"</code>, and <code>data-disabled="true"</code>.</dd>
<dt><code>&lt;span data-shortcut&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Shortcut hint aligned to the inline end of the item.</dd>
<dt><code>&lt;span data-indicator&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Item indicator. It becomes visible with <code>data-checked="true"</code> or <code>aria-selected="true"</code>.</dd>
<dt><code>&lt;hr role="separator"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Separator between groups/items. Hidden while filtering.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `command.refresh()` | Method | Rescans command items after children change inside the existing `role="menu"` list. |
## Examples
### Basic
A simple command menu in a dialog.
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-basic').showModal()">Open Menu</button>
<dialog id="command-basic" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
<div class="command">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="command-basic-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-basic-menu" />
</header>
<div role="menu" id="command-basic-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="command-basic-suggestions">
<span role="heading" id="command-basic-suggestions">Suggestions</span>
<div role="menuitem" data-filter="Calendar">Calendar</div>
<div role="menuitem" data-filter="Search Emoji">Search Emoji</div>
<div role="menuitem" data-filter="Calculator">Calculator</div>
</div>
</div>
</div>
</dialog>
</Preview>
### Shortcuts
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-shortcuts-dialog').showModal()">Open Menu</button>
<dialog id="command-shortcuts-dialog" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
<div class="command">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="command-shortcuts-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-shortcuts-menu" />
</header>
<div role="menu" id="command-shortcuts-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="command-shortcuts-settings">
<span role="heading" id="command-shortcuts-settings">Settings</span>
<div role="menuitem" data-filter="Profile" data-keywords="user account"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
<span>Profile</span>
<span data-shortcut>⌘P</span>
</div>
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
<span>Billing</span>
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem" data-filter="Settings" data-keywords="config preferences"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
<span>Settings</span>
<span data-shortcut>⌘S</span>
</div>
</div>
</div>
</div>
</dialog>
</Preview>
### Groups
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-groups-dialog').showModal()">Open Menu</button>
<dialog id="command-groups-dialog" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
<div class="command">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="command-groups-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-groups-menu" />
</header>
<div role="menu" id="command-groups-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="command-groups-suggestions">
<span role="heading" id="command-groups-suggestions">Suggestions</span>
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
<span>Calendar</span>
</div>
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction"><svg class="lucide lucide-smile" 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"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
<span>Search Emoji</span>
</div>
<div role="menuitem" data-filter="Calculator"><svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
<span>Calculator</span>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="command-groups-settings">
<span role="heading" id="command-groups-settings">Settings</span>
<div role="menuitem" data-filter="Profile" data-keywords="user account"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
<span>Profile</span>
<span data-shortcut>⌘P</span>
</div>
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
<span>Billing</span>
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem" data-filter="Settings" data-keywords="config preferences"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
<span>Settings</span>
<span data-shortcut>⌘S</span>
</div>
</div>
</div>
</div>
</dialog>
</Preview>
### Scrollable
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-scrollable-dialog').showModal()">Open Menu</button>
<dialog id="command-scrollable-dialog" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
<div class="command">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="command-scrollable-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-scrollable-menu" />
</header>
<div role="menu" id="command-scrollable-menu" aria-orientation="vertical" data-empty="No results found.">
<div role="group" aria-labelledby="command-scrollable-navigation">
<span role="heading" id="command-scrollable-navigation">Navigation</span>
<div role="menuitem" data-filter="Home"><svg class="lucide lucide-home" 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"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" /><path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /></svg><span>Home</span><span data-shortcut>⌘H</span></div>
<div role="menuitem" data-filter="Inbox"><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg><span>Inbox</span><span data-shortcut>⌘I</span></div>
<div role="menuitem" data-filter="Documents"><svg class="lucide lucide-file-text" 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"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 9H8" /><path d="M16 13H8" /><path d="M16 17H8" /></svg><span>Documents</span><span data-shortcut>⌘D</span></div>
<div role="menuitem" data-filter="Folders"><svg class="lucide lucide-folder" 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"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" /></svg><span>Folders</span><span data-shortcut>⌘F</span></div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="command-scrollable-actions">
<span role="heading" id="command-scrollable-actions">Actions</span>
<div role="menuitem" data-filter="New File"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg><span>New File</span><span data-shortcut>⌘N</span></div>
<div role="menuitem" data-filter="New Folder"><svg class="lucide lucide-folder-plus" 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"><path d="M12 10v6" /><path d="M9 13h6" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" /></svg><span>New Folder</span><span data-shortcut>⇧⌘N</span></div>
<div role="menuitem" data-filter="Copy"><svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg><span>Copy</span><span data-shortcut>⌘C</span></div>
<div role="menuitem" data-filter="Cut"><svg class="lucide lucide-scissors" 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"><circle cx="6" cy="6" r="3" /><path d="M8.12 8.12 12 12" /><path d="M20 4 8.12 15.88" /><circle cx="6" cy="18" r="3" /><path d="M14.8 14.8 20 20" /></svg><span>Cut</span><span data-shortcut>⌘X</span></div>
<div role="menuitem" data-filter="Paste"><svg class="lucide lucide-clipboard-paste" 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"><path d="M11 14h10" /><path d="M16 4h2a2 2 0 0 1 2 2v1.344" /><path d="m17 18 4-4-4-4" /><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113" /><rect x="8" y="2" width="8" height="4" rx="1" /></svg><span>Paste</span><span data-shortcut>⌘V</span></div>
<div role="menuitem" data-filter="Delete"><svg class="lucide lucide-trash" 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"><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg><span>Delete</span><span data-shortcut>⌫</span></div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="command-scrollable-view">
<span role="heading" id="command-scrollable-view">View</span>
<div role="menuitem" data-filter="Grid View"><svg class="lucide lucide-layout-grid" 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"><rect width="7" height="7" x="3" y="3" rx="1" /><rect width="7" height="7" x="14" y="3" rx="1" /><rect width="7" height="7" x="14" y="14" rx="1" /><rect width="7" height="7" x="3" y="14" rx="1" /></svg><span>Grid View</span></div>
<div role="menuitem" data-filter="List View"><svg class="lucide lucide-list" 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"><path d="M3 5h.01" /><path d="M3 12h.01" /><path d="M3 19h.01" /><path d="M8 5h13" /><path d="M8 12h13" /><path d="M8 19h13" /></svg><span>List View</span></div>
<div role="menuitem" data-filter="Zoom In"><svg class="lucide lucide-zoom-in" 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"><circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line x1="11" x2="11" y1="8" y2="14" /><line x1="8" x2="14" y1="11" y2="11" /></svg><span>Zoom In</span><span data-shortcut>⌘+</span></div>
<div role="menuitem" data-filter="Zoom Out"><svg class="lucide lucide-zoom-out" 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"><circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line x1="8" x2="14" y1="11" y2="11" /></svg><span>Zoom Out</span><span data-shortcut>⌘-</span></div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="command-scrollable-account">
<span role="heading" id="command-scrollable-account">Account</span>
<div role="menuitem" data-filter="Profile"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg><span>Profile</span><span data-shortcut>⌘P</span></div>
<div role="menuitem" data-filter="Billing"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg><span>Billing</span><span data-shortcut>⌘B</span></div>
<div role="menuitem" data-filter="Settings"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg><span>Settings</span><span data-shortcut>⌘S</span></div>
<div role="menuitem" data-filter="Notifications"><svg class="lucide lucide-bell" 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"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg><span>Notifications</span></div>
<div role="menuitem" data-filter="Help & Support"><svg class="lucide lucide-circle-help" 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"><circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><path d="M12 17h.01" /></svg><span>Help &amp; Support</span></div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="command-scrollable-tools">
<span role="heading" id="command-scrollable-tools">Tools</span>
<div role="menuitem" data-filter="Calculator"><svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg><span>Calculator</span></div>
<div role="menuitem" data-filter="Calendar"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg><span>Calendar</span></div>
<div role="menuitem" data-filter="Image Editor"><svg class="lucide lucide-image" 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"><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" /></svg><span>Image Editor</span></div>
<div role="menuitem" data-filter="Code Editor"><svg class="lucide lucide-code" 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"><path d="m16 18 6-6-6-6" /><path d="m8 6-6 6 6 6" /></svg><span>Code Editor</span></div>
</div>
</div>
</div>
</dialog>
</Preview>
## RTL
Add `dir="rtl"` to the command root or an ancestor.
<Preview class="w-full max-w-sm">
<div dir="rtl">
<div id="command-rtl" class="command border" aria-label="Command menu">
<header><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="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
<input type="text" id="command-rtl-input" placeholder="اكتب أمرًا أو ابحث..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-rtl-menu" />
</header>
<div role="menu" id="command-rtl-menu" aria-orientation="vertical" data-empty="لم يتم العثور على نتائج.">
<div role="group" aria-labelledby="command-rtl-suggestions">
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
<div role="menuitem" data-filter="Calendar"><svg class="lucide lucide-calendar" 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"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
<span>التقويم</span>
<span data-shortcut>⌘K</span>
</div>
<div role="menuitem" data-filter="Search Emoji"><svg class="lucide lucide-smile" 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"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
<span>البحث عن الرموز التعبيرية</span>
</div>
<div role="menuitem" aria-disabled="true" data-filter="Calculator"><svg class="lucide lucide-calculator" 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"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
<span>الآلة الحاسبة</span>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="command-rtl-settings">
<span role="heading" id="command-rtl-settings">الإعدادات</span>
<div role="menuitem" data-filter="Profile"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
<span>الملف الشخصي</span>
<span data-shortcut>⌘P</span>
</div>
<div role="menuitem" data-filter="Billing"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
<span>الفوترة</span>
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem" data-filter="Settings"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
<span>الإعدادات</span>
<span data-shortcut>⌘S</span>
</div>
</div>
</div>
</div>
</div>
</Preview>
+258
View File
@@ -0,0 +1,258 @@
# Dialog
<Preview>
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn" data-variant="outline">Open Dialog</button>
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
<div class="sm:max-w-sm">
<header>
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
</header>
<section>
<form class="grid gap-4">
<div class="grid gap-3">
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
</div>
<div class="grid gap-3">
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
</div>
</form>
</section>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
</footer>
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" aria-label="Close dialog" onclick="this.closest('dialog').close()"><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="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg></button>
</div>
</dialog>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#dialog" }}>
This component ships a `dialog()` macro for Jinja and Nunjucks.
</Callout>
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Dialog component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/dialog.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your dialog HTML">
Basecoat uses the native `<dialog>` element and `showModal()`. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.
```html
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn" data-variant="outline">Open Dialog</button>
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
<div class="sm:max-w-sm">
<header>
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
</header>
<section>
<form class="grid gap-4">
<div class="grid gap-3">
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
</div>
<div class="grid gap-3">
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
</div>
</form>
</section>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
</footer>
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" aria-label="Close dialog" onclick="this.closest('dialog').close()"><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="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg></button>
</div>
</dialog>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;button type="button" onclick="dialog.showModal()"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
<dt><code>&lt;dialog class="dialog" id="&#123; DIALOG_ID &#125;"&gt;</code></dt>
<dd>
Native modal dialog. Add <code>aria-labelledby="&#123; TITLE_ID &#125;"</code> and <code>aria-describedby="&#123; DESCRIPTION_ID &#125;"</code> when title and description are present. Add width utilities to the inner <code>&lt;div&gt;</code> when a dialog needs a custom size. The macro also adds backdrop-click close handling.
<dl>
<dt><code>&lt;div&gt;</code></dt>
<dd>
Dialog content surface.
<dl>
<dt><code>&lt;header&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>
Dialog header.
<dl>
<dt><code>&lt;h2 id="&#123; TITLE_ID &#125;"&gt;</code></dt>
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
<dt><code>&lt;p id="&#123; DESCRIPTION_ID &#125;"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
</dl>
</dd>
<dt><code>&lt;section&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
<dt><code>&lt;button type="button" onclick="this.closest('dialog').close()"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Close button. You can also wrap a button in <code>&lt;form method="dialog"&gt;</code>.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
## Examples
### Custom close button
<Preview>
<button type="button" onclick="document.getElementById('dialog-custom-close').showModal()" class="btn" data-variant="outline">Share</button>
<dialog id="dialog-custom-close" class="dialog" aria-labelledby="dialog-custom-close-title" aria-describedby="dialog-custom-close-description" onclick="if (event.target === this) this.close()">
<div class="sm:max-w-md">
<header>
<h2 id="dialog-custom-close-title">Share link</h2>
<p id="dialog-custom-close-description">Anyone who has this link will be able to view this.</p>
</header>
<section class="flex items-center gap-2">
<div class="grid flex-1 gap-2">
<label class="label sr-only" for="dialog-custom-close-link">Link</label>
<input class="input" id="dialog-custom-close-link" value="https://basecoatui.com/installation/" readonly />
</div>
</section>
<footer class="sm:justify-start">
<button class="btn" onclick="this.closest('dialog').close()">Close</button>
</footer>
</div>
</dialog>
</Preview>
### No close button
<Preview>
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn" data-variant="outline">No Close Button</button>
<dialog id="dialog-no-close" class="dialog" aria-labelledby="dialog-no-close-title" aria-describedby="dialog-no-close-description" onclick="if (event.target === this) this.close()">
<div>
<header>
<h2 id="dialog-no-close-title">No Close Button</h2>
<p id="dialog-no-close-description">This dialog doesn't have a close button in the top-right corner.</p>
</header>
</div>
</dialog>
</Preview>
### Sticky footer
<Preview>
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn" data-variant="outline">Sticky Footer</button>
<dialog id="dialog-sticky-footer" class="dialog" aria-labelledby="dialog-sticky-footer-title" aria-describedby="dialog-sticky-footer-description" onclick="if (event.target === this) this.close()">
<div>
<header>
<h2 id="dialog-sticky-footer-title">Sticky Footer</h2>
<p id="dialog-sticky-footer-description">This dialog has a sticky footer that stays visible while the content scrolls.</p>
</header>
<section class="-mx-4 max-h-[50vh] overflow-y-auto px-4 scrollbar">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
<p class="mb-4 leading-normal">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</section>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Close</button>
</footer>
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" aria-label="Close dialog" onclick="this.closest('dialog').close()"><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="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg></button>
</div>
</dialog>
</Preview>
### Scrollable content
<Preview>
<button type="button" onclick="document.getElementById('dialog-scrollable').showModal()" class="btn" data-variant="outline">Scrollable Content</button>
<dialog id="dialog-scrollable" class="dialog" aria-labelledby="dialog-scrollable-title" aria-describedby="dialog-scrollable-description" onclick="if (event.target === this) this.close()">
<div>
<header>
<h2 id="dialog-scrollable-title">Scrollable Content</h2>
<p id="dialog-scrollable-description">This is a dialog with scrollable content.</p>
</header>
<section class="-mx-4 max-h-[50vh] overflow-y-auto px-4 scrollbar">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
<p class="mb-4 leading-normal">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
<p class="mb-4 leading-normal">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</section>
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" aria-label="Close dialog" onclick="this.closest('dialog').close()"><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="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg></button>
</div>
</dialog>
</Preview>
### RTL
Dialog positioning and close button placement use logical properties. Set `dir="rtl"` on the dialog or a parent element.
<Preview>
<div dir="rtl">
<button type="button" onclick="document.getElementById('dialog-rtl').showModal()" class="btn" data-variant="outline">فتح الحوار</button>
<dialog id="dialog-rtl" class="dialog" aria-labelledby="dialog-rtl-title" aria-describedby="dialog-rtl-description" onclick="if (event.target === this) this.close()">
<div class="sm:max-w-sm">
<header>
<h2 id="dialog-rtl-title">تعديل الملف الشخصي</h2>
<p id="dialog-rtl-description">قم بإجراء تغييرات على ملفك الشخصي هنا. انقر فوق حفظ عند الانتهاء.</p>
</header>
<section>
<form class="grid gap-4">
<div class="grid gap-3">
<label class="label" for="dialog-rtl-name">الاسم</label>
<input class="input" id="dialog-rtl-name" name="name" value="Pedro Duarte" />
</div>
<div class="grid gap-3">
<label class="label" for="dialog-rtl-username">اسم المستخدم</label>
<input class="input" id="dialog-rtl-username" name="username" value="@peduarte" />
</div>
</form>
</section>
<footer>
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
<button class="btn" onclick="this.closest('dialog').close()">حفظ التغييرات</button>
</footer>
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" aria-label="Close dialog" onclick="this.closest('dialog').close()"><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="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg></button>
</div>
</dialog>
</div>
</Preview>
+312
View File
@@ -0,0 +1,312 @@
# Drawer
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('demo-drawer').showModal()">Open Drawer</button>
<dialog id="demo-drawer" class="drawer" aria-labelledby="demo-drawer-title" aria-describedby="demo-drawer-description">
<article>
<header class="mx-auto w-full max-w-sm">
<h2 id="demo-drawer-title">Move Goal</h2>
<p id="demo-drawer-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<div class="mx-auto w-full max-w-sm">
<div class="flex items-center justify-center gap-6">
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Decrease"><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" aria-hidden="true"><path d="M5 12h14" /></svg></button>
<div class="text-center">
<div class="text-7xl font-bold">350</div>
<div class="text-muted-foreground text-xs uppercase">Calories/day</div>
</div>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Increase"><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" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
</div>
</section>
<footer class="mx-auto w-full max-w-sm">
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Drawer component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/drawer.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Drawer script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/drawer.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your drawer HTML">
Basecoat maps shadcn/ui's Vaul Drawer to a native `<dialog>` element. This keeps modality, focus handling, and inert page content in the browser platform. The Drawer script adds safe backdrop-click close and lets the exit animation finish before calling the native close. Drag gestures, snap points, and the drag handle are intentionally not included.
```html
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('demo-drawer').showModal()">Open Drawer</button>
<dialog id="demo-drawer" class="drawer" aria-labelledby="demo-drawer-title" aria-describedby="demo-drawer-description">
<article>
<header class="mx-auto w-full max-w-sm">
<h2 id="demo-drawer-title">Move Goal</h2>
<p id="demo-drawer-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<div class="mx-auto w-full max-w-sm">
<div class="flex items-center justify-center gap-6">
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Decrease">...</button>
<div class="text-center">
<div class="text-7xl font-bold">350</div>
<div class="text-muted-foreground text-xs uppercase">Calories/day</div>
</div>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Increase">...</button>
</div>
</div>
</section>
<footer class="mx-auto w-full max-w-sm">
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
```
</Step>
</Steps>
### HTML structure
<Callout type="warning" title="No drag handle" icon="triangle-alert">
Basecoat does not implement Vaul's drag-to-close gesture, snap points, or drag handle. Drawers close with outside click, Escape, or explicit close controls.
</Callout>
<dl>
<dt><code>&lt;button type="button" onclick="drawer.showModal()"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
<dt><code>&lt;dialog class="drawer" data-side="bottom|top|left|right"&gt;</code></dt>
<dd>
Native modal drawer. Omit <code>data-side</code> for the default bottom drawer. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present. JavaScript adds the <code>data-drawer-initialized</code> flag and manages animated close behavior.
<dl>
<dt><code>&lt;article&gt;</code></dt>
<dd>
Drawer content surface.
<dl>
<dt><code>&lt;header&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Drawer header with a title and optional description.</dd>
<dt><code>&lt;section&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Scrollable body/content area.</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Action area pinned after the drawer body.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
## Examples
### Sides
Use `data-side` to set the viewport side. Available values are `top`, `right`, `bottom`, and `left`.
<Preview>
<div class="flex flex-wrap gap-2">
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-top').showModal()">Top</button>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-right').showModal()">Right</button>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-bottom').showModal()">Bottom</button>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-left').showModal()">Left</button>
</div>
<dialog id="drawer-side-top" class="drawer" data-side="top" aria-labelledby="drawer-side-top-title" aria-describedby="drawer-side-top-description">
<article class="max-h-[50vh]">
<header>
<h2 id="drawer-side-top-title">Move Goal</h2>
<p id="drawer-side-top-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
<dialog id="drawer-side-right" class="drawer" data-side="right" aria-labelledby="drawer-side-right-title" aria-describedby="drawer-side-right-description">
<article>
<header>
<h2 id="drawer-side-right-title">Move Goal</h2>
<p id="drawer-side-right-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
<dialog id="drawer-side-bottom" class="drawer" data-side="bottom" aria-labelledby="drawer-side-bottom-title" aria-describedby="drawer-side-bottom-description">
<article class="max-h-[50vh]">
<header>
<h2 id="drawer-side-bottom-title">Move Goal</h2>
<p id="drawer-side-bottom-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
<dialog id="drawer-side-left" class="drawer" data-side="left" aria-labelledby="drawer-side-left-title" aria-describedby="drawer-side-left-description">
<article>
<header>
<h2 id="drawer-side-left-title">Move Goal</h2>
<p id="drawer-side-left-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
</Preview>
### Scrollable content
Keep actions visible while the drawer body scrolls.
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-scrollable').showModal()">Scrollable Content</button>
<dialog id="drawer-scrollable" class="drawer" data-side="right" aria-labelledby="drawer-scrollable-title" aria-describedby="drawer-scrollable-description">
<article>
<header>
<h2 id="drawer-scrollable-title">Move Goal</h2>
<p id="drawer-scrollable-description">Set your daily activity goal.</p>
</header>
<section class="px-4">
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>
<footer>
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
</footer>
</article>
</dialog>
</Preview>
### RTL
Drawer sides are physical viewport sides. Content spacing and text alignment follow document direction.
<Preview>
<div dir="rtl">
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-rtl').showModal()">فتح الدرج</button>
<dialog id="drawer-rtl" class="drawer" aria-labelledby="drawer-rtl-title" aria-describedby="drawer-rtl-description">
<article>
<header class="mx-auto w-full max-w-sm">
<h2 id="drawer-rtl-title">نقل الهدف</h2>
<p id="drawer-rtl-description">حدد هدف نشاطك اليومي.</p>
</header>
<section class="px-4">
<div class="mx-auto w-full max-w-sm">
<div class="flex items-center justify-center gap-6">
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="تقليل"><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" aria-hidden="true"><path d="M5 12h14" /></svg></button>
<div class="text-center">
<div class="text-7xl font-bold">٣٥٠</div>
<div class="text-muted-foreground text-xs uppercase">سعرات حرارية/يوم</div>
</div>
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="زيادة"><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" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
</div>
</div>
</section>
<footer class="mx-auto w-full max-w-sm">
<button type="button" class="btn" onclick="this.closest('dialog').close()">إرسال</button>
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
</footer>
</article>
</dialog>
</div>
</Preview>
+510
View File
@@ -0,0 +1,510 @@
# Dropdown Menu
<Preview>
<div id="demo-dropdown-menu" class="dropdown-menu">
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="w-40">
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
<div role="group" aria-labelledby="demo-dropdown-account">
<div role="heading" id="demo-dropdown-account">My Account</div>
<div role="menuitem">
<span>Profile</span>
<kbd>⇧⌘P</kbd>
</div>
<div role="menuitem">
<span>Billing</span>
<kbd>⌘B</kbd>
</div>
<div role="menuitem">
<span>Settings</span>
<kbd>⌘S</kbd>
</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem">Team</div>
<div role="menuitem">
<span>New Team</span>
<kbd>⌘+T</kbd>
</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem">
<span>Log out</span>
<kbd>⇧⌘Q</kbd>
</div>
</div>
</div>
</div>
</div>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#dropdown_menu" }}>
This component ships a `dropdown_menu()` macro for Jinja and Nunjucks.
</Callout>
Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Dropdown Menu component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/dropdown-menu.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Dropdown Menu script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/dropdown-menu.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your dropdown menu HTML">
```html
<div id="demo-dropdown-menu" class="dropdown-menu">
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="w-40">
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
<div role="group" aria-labelledby="demo-dropdown-account">
<div role="heading" id="demo-dropdown-account">My Account</div>
<div role="menuitem">
<span>Profile</span>
<kbd>⇧⌘P</kbd>
</div>
<div role="menuitem">
<span>Billing</span>
<kbd>⌘B</kbd>
</div>
<div role="menuitem">
<span>Settings</span>
<kbd>⌘S</kbd>
</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem">Team</div>
<div role="menuitem">
<span>New Team</span>
<kbd>⌘+T</kbd>
</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem">
<span>Log out</span>
<kbd>⇧⌘Q</kbd>
</div>
</div>
</div>
</div>
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="dropdown-menu"&gt;</code></dt>
<dd>
Relative wrapper for the trigger and inline menu content.
<dl>
<dt><code>&lt;button aria-haspopup="menu" aria-expanded="false"&gt;</code></dt>
<dd>Trigger button. The script toggles <code>aria-expanded</code> and manages keyboard navigation.</dd>
<dt><code>&lt;div data-popover aria-hidden="true"&gt;</code></dt>
<dd>
Menu content popover. Set <code>data-side="top|right|bottom|left|inline-start|inline-end"</code> and <code>data-align="start|center|end"</code> to control placement.
<dl>
<dt><code>&lt;div role="menu"&gt;</code></dt>
<dd>
Container for menu items, groups, labels, and separators.
<dl>
<dt><code>&lt;div role="group" aria-labelledby="&#123; HEADING_ID &#125;"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Groups related menu items.</dd>
<dt><code>&lt;div role="heading" id="&#123; HEADING_ID &#125;"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Group heading/label.</dd>
<dt><code>&lt;div role="menuitem"&gt;</code></dt>
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
<dt><code>&lt;div role="menuitemcheckbox" aria-checked="true"&gt;</code></dt>
<dd>Checkbox-style item. Add a child with <code>data-indicator</code> for the checked icon.</dd>
<dt><code>&lt;div role="menuitemradio" aria-checked="true"&gt;</code></dt>
<dd>Radio-style item. Add a child with <code>data-indicator</code> for the selected icon.</dd>
<dt><code>&lt;kbd&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Shortcut hint aligned to the inline end of the item.</dd>
<dt><code>&lt;hr role="separator"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Separator between groups or options.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `dropdown.open()` | Method | Opens the menu. |
| `dropdown.close()` | Method | Closes the menu. |
| `dropdown.toggle()` | Method | Toggles the menu. |
| `dropdown.refresh()` | Method | Rescans menu items after children change inside the existing `role="menu"` element. |
## Examples
### Basic
<Preview>
<div id="dropdown-basic" class="dropdown-menu">
<button type="button" id="dropdown-basic-trigger" aria-haspopup="menu" aria-controls="dropdown-basic-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="dropdown-basic-popover" data-popover aria-hidden="true" class="min-w-32">
<div role="menu" id="dropdown-basic-menu" aria-labelledby="dropdown-basic-trigger">
<div role="group" aria-labelledby="dropdown-basic-account">
<div role="heading" id="dropdown-basic-account">My Account</div>
<div role="menuitem">Profile</div>
<div role="menuitem">Billing</div>
<div role="menuitem">Settings</div>
</div>
<hr role="separator" />
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
</div>
</div>
</div>
</Preview>
### Shortcuts
<Preview>
<div id="dropdown-shortcuts" class="dropdown-menu">
<button type="button" id="dropdown-shortcuts-trigger" aria-haspopup="menu" aria-controls="dropdown-shortcuts-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="dropdown-shortcuts-popover" data-popover aria-hidden="true" class="min-w-46">
<div role="menu" id="dropdown-shortcuts-menu" aria-labelledby="dropdown-shortcuts-trigger">
<div role="group" aria-labelledby="dropdown-shortcuts-account">
<div role="heading" id="dropdown-shortcuts-account">My Account</div>
<div role="menuitem">
<span>Profile</span>
<kbd>⇧⌘P</kbd>
</div>
<div role="menuitem">
<span>Billing</span>
<kbd>⌘B</kbd>
</div>
<div role="menuitem">
<span>Settings</span>
<kbd>⌘S</kbd>
</div>
</div>
<hr role="separator" />
<div role="menuitem">
<span>Log out</span>
<kbd>⇧⌘Q</kbd>
</div>
</div>
</div>
</div>
</Preview>
### Icons
<Preview>
<div id="dropdown-icons" class="dropdown-menu">
<button type="button" id="dropdown-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-icons-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="dropdown-icons-popover" data-popover aria-hidden="true" class="min-w-32">
<div role="menu" id="dropdown-icons-menu" aria-labelledby="dropdown-icons-trigger">
<div role="menuitem"><svg class="lucide lucide-user" 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"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
Profile
</div>
<div role="menuitem"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
Billing
</div>
<div role="menuitem"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
Settings
</div>
<hr role="separator" />
<div role="menuitem" data-variant="destructive"><svg class="lucide lucide-log-out" 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"><path d="m16 17 5-5-5-5" /><path d="M21 12H9" /><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /></svg>
Log out
</div>
</div>
</div>
</div>
</Preview>
### Checkboxes
<Preview>
<div id="dropdown-checkboxes" class="dropdown-menu">
<button type="button" id="dropdown-checkboxes-trigger" aria-haspopup="menu" aria-controls="dropdown-checkboxes-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="dropdown-checkboxes-popover" data-popover aria-hidden="true" class="w-40">
<div role="menu" id="dropdown-checkboxes-menu" aria-labelledby="dropdown-checkboxes-trigger">
<div role="group" aria-labelledby="dropdown-checkboxes-label">
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
<div role="menuitemcheckbox" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
Status Bar
</div>
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
Activity Bar
</div>
<div role="menuitemcheckbox" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
Panel
</div>
</div>
</div>
</div>
</div>
</Preview>
### Checkboxes Icons
<Preview>
<div id="dropdown-checkboxes-icons" class="dropdown-menu">
<button type="button" id="dropdown-checkboxes-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-checkboxes-icons-menu" aria-expanded="false" class="btn" data-variant="outline">Notifications</button>
<div id="dropdown-checkboxes-icons-popover" data-popover aria-hidden="true" class="w-48">
<div role="menu" id="dropdown-checkboxes-icons-menu" aria-labelledby="dropdown-checkboxes-icons-trigger">
<div role="group" aria-labelledby="dropdown-checkboxes-icons-label">
<div role="heading" id="dropdown-checkboxes-icons-label">Notification Preferences</div>
<div role="menuitemcheckbox" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
<svg class="lucide lucide-mail" 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"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" width="20" height="16" rx="2" /></svg>
Email notifications
</div>
<div role="menuitemcheckbox" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
<svg class="lucide lucide-message-square" 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"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>
SMS notifications
</div>
<div role="menuitemcheckbox" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
<svg class="lucide lucide-bell" 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"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg>
Push notifications
</div>
</div>
</div>
</div>
</div>
</Preview>
### Radio group
<Preview>
<div id="dropdown-radio-group" class="dropdown-menu">
<button type="button" id="dropdown-radio-group-trigger" aria-haspopup="menu" aria-controls="dropdown-radio-group-menu" aria-expanded="false" class="btn" data-variant="outline">Open</button>
<div id="dropdown-radio-group-popover" data-popover aria-hidden="true" class="min-w-32">
<div role="menu" id="dropdown-radio-group-menu" aria-labelledby="dropdown-radio-group-trigger">
<div role="group" aria-labelledby="dropdown-radio-label">
<div role="heading" id="dropdown-radio-label">Panel Position</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
Top
</div>
<div role="menuitemradio" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
Bottom
</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
Right
</div>
</div>
</div>
</div>
</div>
</Preview>
### Radio Icons
<Preview>
<div id="dropdown-radio-icons" class="dropdown-menu">
<button type="button" id="dropdown-radio-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-radio-icons-menu" aria-expanded="false" class="btn" data-variant="outline">Payment Method</button>
<div id="dropdown-radio-icons-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-radio-icons-menu" aria-labelledby="dropdown-radio-icons-trigger">
<div role="group" aria-labelledby="dropdown-radio-icons-label">
<div role="heading" id="dropdown-radio-icons-label">Select Payment Method</div>
<div role="menuitemradio" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
<svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
Credit Card
</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
<svg class="lucide lucide-wallet" 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"><path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" /><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" /></svg>
PayPal
</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
<svg class="lucide lucide-building-2" 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"><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" /><path d="M6 12H4a2 2 0 0 0-2 2v8h20v-8a2 2 0 0 0-2-2h-2" /><path d="M10 6h4" /><path d="M10 10h4" /><path d="M10 14h4" /><path d="M10 18h4" /></svg>
Bank Transfer
</div>
</div>
</div>
</div>
</div>
</Preview>
### Destructive
<Preview>
<div id="dropdown-destructive" class="dropdown-menu">
<button type="button" id="dropdown-destructive-trigger" aria-haspopup="menu" aria-controls="dropdown-destructive-menu" aria-expanded="false" class="btn" data-variant="outline">Actions</button>
<div id="dropdown-destructive-popover" data-popover aria-hidden="true" class="min-w-32">
<div role="menu" id="dropdown-destructive-menu" aria-labelledby="dropdown-destructive-trigger">
<div role="group">
<div role="menuitem"><svg class="lucide lucide-pencil" 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"><path d="M12 20h9" /><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z" /></svg>
Edit
</div>
<div role="menuitem"><svg class="lucide lucide-share" 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"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /><polyline points="16 6 12 2 8 6" /><line x1="12" x2="12" y1="2" y2="15" /></svg>
Share
</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem" data-variant="destructive"><svg class="lucide lucide-trash" 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"><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /></svg>
Delete
</div>
</div>
</div>
</div>
</div>
</Preview>
### Avatar
<Preview>
<div id="dropdown-avatar" class="dropdown-menu">
<button type="button" id="dropdown-avatar-trigger" aria-haspopup="menu" aria-controls="dropdown-avatar-menu" aria-expanded="false" class="btn rounded-full" data-variant="ghost" data-size="icon">
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="shadcn" />
<span>LR</span>
</span>
</button>
<div id="dropdown-avatar-popover" data-popover aria-hidden="true" data-align="end">
<div role="menu" id="dropdown-avatar-menu" aria-labelledby="dropdown-avatar-trigger">
<div role="group">
<div role="menuitem"><svg class="lucide lucide-badge-check" 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"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" /><path d="m9 12 2 2 4-4" /></svg>
Account
</div>
<div role="menuitem"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
Billing
</div>
<div role="menuitem"><svg class="lucide lucide-bell" 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"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg>
Notifications
</div>
</div>
<hr role="separator" />
<div role="menuitem"><svg class="lucide lucide-log-out" 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"><path d="m16 17 5-5-5-5" /><path d="M21 12H9" /><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /></svg>
Sign Out
</div>
</div>
</div>
</div>
</Preview>
### RTL
Dropdown menus support document direction. Set `dir="rtl"` on the dropdown root or a parent element. Use logical alignment data attributes where possible.
<Preview>
<div dir="rtl">
<div id="dropdown-rtl" class="dropdown-menu">
<button type="button" id="dropdown-rtl-trigger" aria-haspopup="menu" aria-controls="dropdown-rtl-menu" aria-expanded="false" class="btn" data-variant="outline">افتح القائمة</button>
<div id="dropdown-rtl-popover" data-popover aria-hidden="true" class="w-36" data-align="end">
<div role="menu" id="dropdown-rtl-menu" aria-labelledby="dropdown-rtl-trigger">
<div role="group" aria-labelledby="dropdown-rtl-team">
<div role="heading" id="dropdown-rtl-team">الفريق</div>
<div role="menuitem">الفريق</div>
<div role="menuitem">
<span>فريق جديد</span>
<kbd>⌘+T</kbd>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="dropdown-rtl-view">
<div role="heading" id="dropdown-rtl-view">عرض</div>
<div role="menuitemcheckbox" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
شريط الحالة
</div>
<div role="menuitemcheckbox" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
شريط النشاط
</div>
<div role="menuitemcheckbox" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
اللوحة
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="dropdown-rtl-position">
<div role="heading" id="dropdown-rtl-position">الموضع</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
أعلى
</div>
<div role="menuitemradio" aria-checked="true">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
أسفل
</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
يمين
</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
يسار
</div>
</div>
<hr role="separator" />
<div role="group">
<div role="menuitem" data-variant="destructive">تسجيل الخروج</div>
</div>
</div>
</div>
</div>
</div>
</Preview>
+210
View File
@@ -0,0 +1,210 @@
# Empty
<Preview>
<section class="empty">
<header>
<figure><svg class="lucide lucide-folder-code" 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"><path d="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg></figure>
<h3>No Projects Yet</h3>
<p>You haven't created any projects yet. Get started by creating your first project.</p>
</header>
<footer>
<div class="flex gap-2">
<button class="btn">Create Project</button>
<button class="btn" data-variant="outline">Import Project</button>
</div>
<a href="#" class="btn text-muted-foreground" data-variant="link" data-size="sm">
Learn More
<svg class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
</a>
</footer>
</section>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Empty component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/empty.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your empty HTML">
Add `class="empty"` to a semantic container. The component styles common children like `header`, `figure`, headings, descriptions, and `footer`.
```html
<section class="empty">
<header>
<figure><svg class="lucide lucide-folder-code" 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"><path d="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg></figure>
<h3>No Projects Yet</h3>
<p>You haven't created any projects yet. Get started by creating your first project.</p>
</header>
<footer>
<div class="flex gap-2">
<button class="btn">Create Project</button>
<button class="btn" data-variant="outline">Import Project</button>
</div>
<a href="#" class="btn text-muted-foreground" data-variant="link" data-size="sm">
Learn More
<svg class="lucide lucide-arrow-up-right" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
</a>
</footer>
</section>
```
</Step>
</Steps>
#### HTML structure
<dl>
<dt><code>&lt;section class="empty"&gt;</code></dt>
<dd>
The empty-state container.
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>
Groups the media, title, and description.
<dl>
<dt><code>&lt;figure&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Icon, avatar, image, or avatar group. An icon-only figure is styled automatically with <code>:has(&gt; svg:only-child)</code>.</dd>
<dt><code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>, or <code>&lt;h4&gt;</code></dt>
<dd>The empty-state title.</dd>
<dt><code>&lt;p&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The empty-state description.</dd>
</dl>
</dd>
<dt><code>&lt;section&gt;</code> or <code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Actions and supporting content.</dd>
</dl>
</dd>
</dl>
## Examples
### Outline
<Preview class="w-full">
<section class="empty border border-dashed">
<header>
<figure><svg class="lucide lucide-cloud" 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"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></svg></figure>
<h3>Cloud Storage Empty</h3>
<p>Upload files to your cloud storage to access them anywhere.</p>
</header>
<footer>
<button class="btn" data-variant="outline" data-size="sm">Upload Files</button>
</footer>
</section>
</Preview>
### Background
<Preview class="w-full">
<section class="empty h-full bg-muted/30">
<header>
<figure><svg class="lucide lucide-bell" 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"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg></figure>
<h3>No Notifications</h3>
<p class="max-w-xs text-pretty">You're all caught up. New notifications will appear here.</p>
</header>
<footer>
<button class="btn" data-variant="outline"><svg class="lucide lucide-refresh-ccw" 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"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" /><path d="M16 16h5v5" /></svg>
Refresh
</button>
</footer>
</section>
</Preview>
### Avatar
<Preview>
<section class="empty">
<header>
<figure class="size-12 rounded-full">
<img class="grayscale" alt="@shadcn" src="https://github.com/shadcn.png" />
</figure>
<h3>User Offline</h3>
<p>This user is currently offline. You can leave a message to notify them or try again later.</p>
</header>
<footer>
<button class="btn" data-size="sm">Leave Message</button>
</footer>
</section>
</Preview>
### Avatar group
<Preview>
<section class="empty">
<header>
<figure class="-space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:rounded-full">
<img alt="@shadcn" src="https://github.com/shadcn.png" />
<img alt="@maxleiter" src="https://github.com/maxleiter.png" />
<img alt="@evilrabbit" src="https://github.com/evilrabbit.png" />
</figure>
<h3>No Team Members</h3>
<p>Invite your team to collaborate on this project.</p>
</header>
<footer>
<button class="btn" data-size="sm"><svg class="lucide lucide-plus" 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"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
<span>Invite Members</span>
</button>
</footer>
</section>
</Preview>
### Input group
<Preview>
<section class="empty">
<header>
<h3>404 - Not Found</h3>
<p>The page you're looking for doesn't exist. Try searching for what you need below.</p>
</header>
<footer>
<div class="input-group sm:w-3/4">
<input type="search" placeholder="Try searching for pages..." />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
<span data-align="end"><kbd class="kbd">/</kbd></span>
</div>
<p>Need help? <a href="#">Contact support</a></p>
</footer>
</section>
</Preview>
### RTL
<Preview>
<section class="empty" dir="rtl">
<header>
<figure><svg class="lucide lucide-folder-code" 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"><path d="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg></figure>
<h3>لا توجد مشاريع بعد</h3>
<p>لم تقم بإنشاء أي مشاريع بعد. ابدأ بإنشاء مشروعك الأول.</p>
</header>
<footer>
<div class="flex flex-row justify-center gap-2">
<button class="btn">إنشاء مشروع</button>
<button class="btn" data-variant="outline">استيراد مشروع</button>
</div>
<a href="#" class="btn text-muted-foreground" data-variant="link" data-size="sm">
تعرف على المزيد
<svg class="lucide lucide-arrow-up-right rtl:rotate-270" data-icon="inline-end" 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"><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
</a>
</footer>
</section>
</Preview>
+454
View File
@@ -0,0 +1,454 @@
# Field
<Preview class="w-full max-w-md">
<form>
<div role="group" aria-label="Checkout fields" class="fieldset">
<fieldset class="fieldset">
<legend>Payment Method</legend>
<p>All transactions are secure and encrypted</p>
<div role="group" aria-label="Payment method fields">
<div role="group" class="field">
<label for="checkout-card-name">Name on Card</label>
<input id="checkout-card-name" type="text" placeholder="Evil Rabbit" required />
</div>
<div role="group" class="field">
<label for="checkout-card-number">Card Number</label>
<input id="checkout-card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="checkout-card-number-description" required />
<p id="checkout-card-number-description">Enter your 16-digit card number</p>
</div>
<div class="grid grid-cols-3 gap-4">
<div role="group" class="field">
<label for="checkout-exp-month">Month</label>
<select id="checkout-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="checkout-exp-year">Year</label>
<select id="checkout-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="checkout-cvv">CVV</label>
<input id="checkout-cvv" type="text" placeholder="123" required />
</div>
</div>
</div>
</fieldset>
<div class="field-separator">
<hr role="separator" />
</div>
<fieldset class="fieldset">
<legend>Billing Address</legend>
<p>The billing address associated with your payment method</p>
<div role="group" class="field" data-orientation="horizontal">
<input id="checkout-same-as-shipping" type="checkbox" checked />
<label for="checkout-same-as-shipping" class="font-normal">Same as shipping address</label>
</div>
</fieldset>
<fieldset class="fieldset">
<div role="group" class="field">
<label for="checkout-comments">Comments</label>
<textarea id="checkout-comments" placeholder="Add any additional comments" rows="3"></textarea>
</div>
</fieldset>
<div role="group" class="field" data-orientation="horizontal">
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn" data-variant="outline">Cancel</button>
</div>
</div>
</form>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Field component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/field.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your field HTML">
Use `class="field"` for one control and `class="fieldset"` for related controls. Use a native `<fieldset>` when the group needs a `<legend>`; otherwise use a labelled `role="group"`.
```html
<fieldset class="fieldset">
<legend>Profile</legend>
<p>This appears on invoices and emails.</p>
<div role="group" aria-label="Profile fields">
<div role="group" class="field">
<label for="name">Full name</label>
<input id="name" autocomplete="off" placeholder="Evil Rabbit" />
<p>This appears on invoices and emails.</p>
</div>
<div role="group" class="field" data-invalid>
<label for="username">Username</label>
<input id="username" autocomplete="off" aria-invalid="true" aria-describedby="username-error" />
<p id="username-error" role="alert">Choose another username.</p>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="newsletter" type="checkbox" role="switch" />
<label for="newsletter">Subscribe to the newsletter</label>
</div>
</div>
</fieldset>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;fieldset class="fieldset"&gt;</code></dt>
<dd>Native group for related fields. Use <code>&lt;legend&gt;</code> for the label and a following <code>&lt;p&gt;</code> for description.</dd>
<dt><code>&lt;div role="group" class="fieldset"&gt;</code></dt>
<dd>Structural group when <code>&lt;fieldset&gt;</code> is not appropriate. Add <code>aria-label</code> or <code>aria-labelledby</code>.</dd>
<dt><code>&lt;div role="group" class="field"&gt;</code></dt>
<dd>Single field wrapper. Add <code>data-orientation</code> when needed.</dd>
<dt><code>&lt;label&gt;</code></dt>
<dd>Label for the control. Use <code>for</code> when the control has an <code>id</code>.</dd>
<dt><code>&lt;input&gt;</code>, <code>&lt;select&gt;</code>, <code>&lt;textarea&gt;</code></dt>
<dd>Native form control. Put <code>aria-invalid="true"</code> on the invalid control, not only on the wrapper.</dd>
<dt><code>&lt;section&gt;</code></dt>
<dd>Optional content wrapper for label and description when the control sits beside text.</dd>
<dt><code>&lt;p&gt;</code></dt>
<dd>Helper text or error text. Use <code>role="alert"</code> for validation errors and connect it with <code>aria-describedby</code>.</dd>
</dl>
## Examples
### Input
<Preview class="w-full max-w-xs">
<fieldset class="fieldset">
<div role="group" aria-label="Account credentials">
<div role="group" class="field">
<label for="username">Username</label>
<input id="username" type="text" placeholder="Max Leiter" aria-describedby="username-description" />
<p id="username-description">Choose a unique username for your account.</p>
</div>
<div role="group" class="field">
<label for="password">Password</label>
<p id="password-description">Must be at least 8 characters long.</p>
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-description" />
</div>
</div>
</fieldset>
</Preview>
### Textarea
<Preview class="w-full max-w-xs">
<fieldset class="fieldset">
<div role="group" class="field">
<label for="feedback">Feedback</label>
<textarea id="feedback" placeholder="Your feedback helps us improve..." rows="4" aria-describedby="feedback-description"></textarea>
<p id="feedback-description">Share your thoughts about our service.</p>
</div>
</fieldset>
</Preview>
### Select
<Preview class="w-full max-w-xs">
<div role="group" class="field">
<label for="department">Department</label>
<select id="department" class="select w-full" aria-describedby="department-description">
<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="department-description">Select your department or area of work.</p>
</div>
</Preview>
### Slider
<Preview class="w-full max-w-xs">
<div role="group" class="field">
<label for="price-range">Price Range</label>
<p id="price-range-description">Set your maximum budget <span id="price-range-output">(up to $800)</span>.</p>
<input id="price-range" type="range" min="0" max="1000" step="10" value="800" aria-describedby="price-range-description" class="input" />
</div>
</Preview>
### Fieldset
<Preview class="w-full max-w-sm">
<fieldset class="fieldset">
<legend>Address Information</legend>
<p>We need your address to deliver your order.</p>
<div role="group" aria-label="Address fields">
<div role="group" class="field">
<label for="street">Street Address</label>
<input id="street" type="text" placeholder="123 Main St" />
</div>
<div class="grid grid-cols-2 gap-4">
<div role="group" class="field">
<label for="city">City</label>
<input id="city" type="text" placeholder="New York" />
</div>
<div role="group" class="field">
<label for="zip">Postal Code</label>
<input id="zip" type="text" placeholder="90502" />
</div>
</div>
</div>
</fieldset>
</Preview>
### Checkbox
<Preview class="w-full max-w-xs">
<div role="group" aria-label="Finder preferences" class="fieldset">
<fieldset class="fieldset">
<legend data-variant="label">Show these items on the desktop</legend>
<p>Select the items you want to show on the desktop.</p>
<div role="group" aria-label="Desktop items">
<div role="group" class="field" data-orientation="horizontal">
<input id="finder-hard-disks" type="checkbox" />
<label for="finder-hard-disks" class="font-normal">Hard disks</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="finder-external-disks" type="checkbox" />
<label for="finder-external-disks" class="font-normal">External disks</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="finder-cds" type="checkbox" />
<label for="finder-cds" class="font-normal">CDs, DVDs, and iPods</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="finder-connected-servers" type="checkbox" />
<label for="finder-connected-servers" class="font-normal">Connected servers</label>
</div>
</div>
</fieldset>
<div class="field-separator">
<hr role="separator" />
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="sync-folders" type="checkbox" checked />
<section>
<label for="sync-folders">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>
</Preview>
### Radio
<Preview class="w-full max-w-xs">
<fieldset class="fieldset">
<legend data-variant="label">Subscription Plan</legend>
<p>Yearly and lifetime plans offer significant savings.</p>
<div role="radiogroup" aria-label="Subscription plan">
<div role="group" class="field" data-orientation="horizontal">
<input id="plan-monthly" type="radio" name="plan" value="monthly" checked />
<label for="plan-monthly" class="font-normal">Monthly ($9.99/month)</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="plan-yearly" type="radio" name="plan" value="yearly" />
<label for="plan-yearly" class="font-normal">Yearly ($99.99/year)</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="plan-lifetime" type="radio" name="plan" value="lifetime" />
<label for="plan-lifetime" class="font-normal">Lifetime ($299.99)</label>
</div>
</div>
</fieldset>
</Preview>
### Switch
<Preview class="w-full max-w-sm">
<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>
</Preview>
### Choice card
Wrap a `.field` inside a `<label>` to create selectable field cards. This works with radio, checkbox, and switch controls.
<Preview class="w-full max-w-xs">
<fieldset class="fieldset">
<legend data-variant="label">Compute Environment</legend>
<p>Select the compute environment for your cluster.</p>
<div role="radiogroup" aria-label="Compute environment">
<label for="compute-kubernetes">
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Kubernetes</h3>
<p>Run GPU workloads on a K8s cluster.</p>
</section>
<input id="compute-kubernetes" type="radio" name="compute" value="kubernetes" checked />
</div>
</label>
<label for="compute-vm">
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Virtual Machine</h3>
<p>Access a cluster to run GPU workloads.</p>
</section>
<input id="compute-vm" type="radio" name="compute" value="vm" />
</div>
</label>
</div>
</fieldset>
</Preview>
### Field group
Stack fields inside a group and use `.field-separator` to divide sections.
<Preview class="w-full max-w-xs">
<div role="group" aria-label="Notification preferences" class="fieldset">
<fieldset class="fieldset">
<legend data-variant="label">Responses</legend>
<p>Get notified when ChatGPT responds to requests that take time, like research or image generation.</p>
<div role="group" aria-label="Response notifications">
<div role="group" class="field" data-orientation="horizontal" data-disabled="true">
<input id="push-responses" type="checkbox" checked disabled />
<label for="push-responses" class="font-normal">Push notifications</label>
</div>
</div>
</fieldset>
<div class="field-separator">
<hr role="separator" />
</div>
<fieldset class="fieldset">
<legend data-variant="label">Tasks</legend>
<p>Get notified when tasks you've created have updates. <a href="#">Manage tasks</a></p>
<div role="group" aria-label="Task notifications">
<div role="group" class="field" data-orientation="horizontal">
<input id="push-tasks" type="checkbox" />
<label for="push-tasks" class="font-normal">Push notifications</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input id="email-tasks" type="checkbox" />
<label for="email-tasks" class="font-normal">Email notifications</label>
</div>
</div>
</fieldset>
</div>
</Preview>
### Responsive layout
Use `data-orientation="responsive"` to stack by default and switch to horizontal in wider containers.
<Preview class="w-full max-w-4xl">
<form>
<fieldset class="fieldset">
<legend>Profile</legend>
<p>Fill in your profile information.</p>
<div class="field-separator">
<hr role="separator" />
</div>
<div role="group" aria-label="Profile fields">
<div role="group" class="field" data-orientation="responsive">
<section>
<label for="responsive-name">Name</label>
<p id="responsive-name-description">Provide your full name for identification</p>
</section>
<input id="responsive-name" placeholder="Evil Rabbit" aria-describedby="responsive-name-description" required />
</div>
<div class="field-separator">
<hr role="separator" />
</div>
<div role="group" class="field" data-orientation="responsive">
<section>
<label for="responsive-message">Message</label>
<p id="responsive-message-description">You can write your message here. Keep it short, preferably under 100 characters.</p>
</section>
<textarea id="responsive-message" placeholder="Hello, world!" rows="4" aria-describedby="responsive-message-description" required class="min-h-[100px] resize-none sm:min-w-[300px]"></textarea>
</div>
<div class="field-separator">
<hr role="separator" />
</div>
<div role="group" class="field" data-orientation="responsive">
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn" data-variant="outline">Cancel</button>
</div>
</div>
</fieldset>
</form>
</Preview>
### Validation and errors
Add `data-invalid` to `.field` and `aria-invalid="true"` to the invalid control.
<Preview class="w-full max-w-xs">
<div role="group" class="field" data-invalid>
<label for="invalid-email">Email</label>
<input id="invalid-email" type="email" value="wrong-email" aria-invalid="true" aria-describedby="invalid-email-error" />
<p id="invalid-email-error" role="alert">Enter a valid email address.</p>
</div>
</Preview>
### RTL
Add `dir="rtl"` to the field, fieldset, or an ancestor.
<Preview class="w-full max-w-xs">
<fieldset class="fieldset" dir="rtl">
<legend>الملف الشخصي</legend>
<p>تظهر هذه المعلومات في حسابك.</p>
<div role="group" aria-label="حقول الملف الشخصي">
<div role="group" class="field">
<label for="field-rtl-name">الاسم الكامل</label>
<input id="field-rtl-name" type="text" placeholder="أرنب شرير" />
<p>اكتب اسمك الأول والأخير.</p>
</div>
</div>
</fieldset>
</Preview>
+367
View File
@@ -0,0 +1,367 @@
# Input Group
<Preview class="w-full max-w-xs">
<div class="input-group">
<input type="text" placeholder="Search..." />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
<span data-align="end">12 results</span>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Input Group component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/input-group.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your input group HTML">
```html
<div class="input-group">
<input type="text" placeholder="Search..." />
<span data-align="start" aria-hidden="true"> <!-- icon --></span>
<span data-align="end">12 results</span>
</div>
```
</Step>
</Steps>
Add `class="input-group"` to the root. Native controls stay semantic and become borderless children inside that shell.
<dl>
<dt><code>&lt;div class="input-group"&gt;</code></dt>
<dd>
The root shell. Add <code>role="group"</code> and an accessible label only when the group itself needs to be named.
<dl>
<dt><code>&lt;input&gt;</code>, <code>&lt;textarea&gt;</code>, <code>&lt;select&gt;</code></dt>
<dd>The native control. Use <code>aria-invalid="true"</code> for invalid state and <code>disabled</code> for disabled state.</dd>
<dt><code>&lt;span&gt;</code>, <code>&lt;svg&gt;</code>, <code>&lt;kbd&gt;</code></dt>
<dd>Decorative or helper content. Use <code>aria-hidden="true"</code> for decorative icons.</dd>
<dt><code>&lt;button&gt;</code>, <code>&lt;div role="group"&gt;</code></dt>
<dd>Interactive actions. Use <code>role="group"</code> with an accessible label when multiple related actions share one side of the input.</dd>
<dt><code>&lt;header&gt;</code>, <code>&lt;footer&gt;</code></dt>
<dd>Header or footer addon rows for vertically oriented input groups.</dd>
<dt><code>data-align="start|end"</code></dt>
<dd>Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use <code>data-orientation="vertical"</code> on the root for header/footer layouts.</dd>
<dt><code>[data-control]</code></dt>
<dd>Escape hatch for custom controls that are not native <code>input</code>, <code>textarea</code>, or <code>select</code>.</dd>
</dl>
</dd>
</dl>
## Examples
Use `data-align="start|end"` on addon wrappers to position them inline. Use `data-orientation="vertical"` with `header` or `footer` for block placement.
### Inline start
<Preview class="w-full max-w-sm">
<div class="field">
<label for="inline-start-input">Input</label>
<div class="input-group">
<input id="inline-start-input" type="text" placeholder="Search..." />
<span role="group" data-align="start" aria-hidden="true"><svg class="text-muted-foreground lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
</div>
<p>Icon positioned at the start.</p>
</div>
</Preview>
### Inline end
<Preview class="w-full max-w-sm">
<div class="field">
<label for="inline-end-input">Input</label>
<div class="input-group">
<input id="inline-end-input" type="password" placeholder="Enter password" />
<span role="group" data-align="end" aria-hidden="true"><svg class="lucide lucide-eye-off" 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"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /><path d="m2 2 20 20" /></svg></span>
</div>
<p>Icon positioned at the end.</p>
</div>
</Preview>
### Block start
<Preview class="w-full max-w-sm">
<div role="group" aria-label="Block start input group examples" class="fieldset">
<div class="field">
<label for="block-start-input">Input</label>
<div class="input-group" data-orientation="vertical">
<input id="block-start-input" type="text" placeholder="Enter your name" />
<header data-align="start">
<span>Full Name</span>
</header>
</div>
<p>Header positioned above the input.</p>
</div>
<div class="field">
<label for="block-start-textarea">Textarea</label>
<div class="input-group" data-orientation="vertical">
<textarea id="block-start-textarea" class="font-mono text-sm" placeholder="console.log('Hello, world!');"></textarea>
<header data-align="start"><svg class="text-muted-foreground lucide lucide-file-code" 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"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /></svg>
<span class="font-mono">script.js</span>
<button type="button" class="btn ml-auto size-6" data-variant="ghost" data-size="icon-sm" aria-label="Copy"><svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg></button>
</header>
</div>
<p>Header positioned above the textarea.</p>
</div>
</div>
</Preview>
### Block end
<Preview class="w-full max-w-sm">
<div role="group" aria-label="Block end input group examples" class="fieldset">
<div class="field">
<label for="block-end-input">Input</label>
<div class="input-group" data-orientation="vertical">
<input id="block-end-input" type="text" placeholder="Enter amount" />
<footer data-align="end">
<span>USD</span>
</footer>
</div>
<p>Footer positioned below the input.</p>
</div>
<div class="field">
<label for="block-end-textarea">Textarea</label>
<div class="input-group" data-orientation="vertical">
<textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
<footer data-align="end">
<span>0/280</span>
<button type="button" class="btn ml-auto" data-size="sm">Post</button>
</footer>
</div>
<p>Footer positioned below the textarea.</p>
</div>
</div>
</Preview>
### Icon
<Preview class="w-full max-w-sm">
<div class="grid gap-6">
<div class="input-group">
<input type="text" placeholder="Search..." />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
</div>
<div class="input-group">
<input type="email" placeholder="Enter your email" />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-mail" 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"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" width="20" height="16" rx="2" /></svg></span>
</div>
<div class="input-group">
<input type="text" placeholder="Card number" />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-credit-card" 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"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg></span>
<span data-align="end" aria-hidden="true"><svg class="lucide lucide-check" 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"><path d="M20 6 9 17l-5-5" /></svg></span>
</div>
<div class="input-group">
<input type="text" placeholder="Card number" />
<span data-align="end" aria-hidden="true"><svg class="lucide lucide-star" 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"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg><svg class="lucide lucide-info" 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"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg></span>
</div>
</div>
</Preview>
### Text
<Preview class="w-full max-w-sm">
<div class="grid gap-6">
<div class="input-group">
<input type="text" placeholder="0.00" />
<span data-align="start">$</span>
<span data-align="end">USD</span>
</div>
<div class="input-group">
<input type="text" placeholder="example.com" />
<span data-align="start">https://</span>
<span data-align="end">.com</span>
</div>
<div class="input-group">
<input type="text" placeholder="Enter your username" />
<span data-align="end">@company.com</span>
</div>
<div class="input-group" data-orientation="vertical">
<textarea placeholder="Enter your message"></textarea>
<footer data-align="end">
<span class="text-xs text-muted-foreground">120 characters left</span>
</footer>
</div>
</div>
</Preview>
### Button
<Preview class="w-full max-w-sm">
<div class="grid gap-6">
<div class="input-group">
<input type="text" readonly placeholder="https://x.com/shadcn" />
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" data-align="end" aria-label="Copy" title="Copy"><svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg></button>
</div>
<div class="input-group [--radius:9999px]">
<button type="button" class="btn" data-variant="secondary" data-size="icon-xs" data-align="start" aria-label="Connection information"><svg class="lucide lucide-info" 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"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg></button>
<span data-align="start" class="text-muted-foreground">https://</span>
<input type="text" id="input-secure-19" />
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" data-align="end" aria-label="Favorite"><svg class="lucide lucide-star" 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"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg></button>
</div>
<div class="input-group">
<input type="text" placeholder="Type to search..." />
<button type="button" class="btn" data-size="xs" data-variant="secondary" data-align="end">Search</button>
</div>
</div>
</Preview>
### Kbd
<Preview class="w-full max-w-sm">
<div class="input-group">
<input type="text" placeholder="Search..." />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
<span data-align="end"><kbd class="kbd">⌘K</kbd></span>
</div>
</Preview>
### Dropdown
<Preview class="w-full max-w-sm">
<div class="grid gap-6">
<div class="input-group">
<input type="text" placeholder="Enter file name" />
<div id="dropdown-menu-601971" class="dropdown-menu" data-align="end">
<button type="button" id="dropdown-menu-601971-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-601971-menu" aria-expanded="false" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Open file actions"><svg class="lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></button>
<div id="dropdown-menu-601971-popover" data-popover aria-hidden="true" data-align="end" class="min-w-32">
<div role="menu" id="dropdown-menu-601971-menu" aria-labelledby="dropdown-menu-601971-trigger">
<div id="dropdown-menu-601971-items-1" role="menuitem">Settings</div>
<div id="dropdown-menu-601971-items-2" role="menuitem">Copy path</div>
<div id="dropdown-menu-601971-items-3" role="menuitem">Open location</div>
</div>
</div>
</div>
</div>
<div class="input-group [--radius:1rem]">
<input type="text" placeholder="Enter search query" />
<div id="dropdown-menu-689868" class="dropdown-menu" data-align="end">
<button type="button" id="dropdown-menu-689868-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-689868-menu" aria-expanded="false" class="btn pr-1.5! text-xs" data-variant="ghost" data-size="xs">
Search In...
<svg class="size-3 lucide lucide-chevron-down" 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"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="dropdown-menu-689868-popover" data-popover aria-hidden="true" data-align="end" class="min-w-32 [--radius:0.95rem]">
<div role="menu" id="dropdown-menu-689868-menu" aria-labelledby="dropdown-menu-689868-trigger">
<div id="dropdown-menu-689868-items-1" role="menuitem">Documentation</div>
<div id="dropdown-menu-689868-items-2" role="menuitem">Blog Posts</div>
<div id="dropdown-menu-689868-items-3" role="menuitem">Changelog</div>
</div>
</div>
</div>
</div>
</div>
</Preview>
### Spinner
<Preview class="w-full max-w-sm">
<div class="grid gap-6">
<div class="input-group">
<input type="text" placeholder="Searching..." />
<span data-align="end"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></span>
</div>
<div class="input-group">
<input type="text" placeholder="Processing..." />
<span data-align="start"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></span>
</div>
<div class="input-group">
<input type="text" placeholder="Saving changes..." />
<span data-align="end">
Saving...
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
</span>
</div>
<div class="input-group">
<input type="text" placeholder="Refreshing data..." />
<span data-align="start" aria-hidden="true"><svg class="animate-spin lucide lucide-loader" 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"><path d="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg></span>
<span data-align="end" class="text-muted-foreground">Please wait...</span>
</div>
</div>
</Preview>
### Textarea
<Preview class="w-full max-w-md">
<div class="input-group min-h-[200px]" data-orientation="vertical">
<textarea placeholder="console.log('Hello, world!');"></textarea>
<footer data-align="end" class="border-t">
<span>Line 1, Column 1</span>
<button type="button" class="btn ml-auto" data-size="sm">
Run
<svg class="lucide lucide-corner-down-left" 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"><path d="M20 4v7a4 4 0 0 1-4 4H4" /><path d="m9 10-5 5 5 5" /></svg>
</button>
</footer>
<header data-align="start" class="border-b"><svg class="text-muted-foreground lucide lucide-file-code" 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"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /></svg>
<span>script.js</span>
<div role="group" aria-label="File actions" class="ml-auto flex gap-1">
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Refresh file"><svg class="lucide lucide-refresh-ccw" 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"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" /><path d="M16 16h5v5" /></svg></button>
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Copy file"><svg class="lucide lucide-copy" 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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg></button>
</div>
</header>
</div>
</Preview>
### Custom input
<Preview class="w-full max-w-sm">
<div class="input-group" data-orientation="vertical">
<textarea data-control placeholder="Autoresize textarea..." class="field-sizing-content min-h-16 resize-none"></textarea>
<footer data-align="end">
<button type="button" class="btn ml-auto" data-size="sm">Submit</button>
</footer>
</div>
</Preview>
### RTL
<Preview class="w-full max-w-sm">
<div dir="rtl" class="grid gap-6">
<div class="input-group">
<input type="text" placeholder="بحث..." />
<span role="group" data-align="start" aria-hidden="true"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
<span role="group" data-align="end">١٢ نتيجة</span>
</div>
<div class="input-group">
<input type="text" placeholder="جاري البحث..." />
<span role="group" data-align="end"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></span>
</div>
<div class="input-group">
<input type="text" placeholder="جاري حفظ التغييرات..." />
<span role="group" data-align="end">
جاري الحفظ...
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
</span>
</div>
<div class="field">
<label for="rtl-textarea">منطقة النص</label>
<div class="input-group" data-orientation="vertical">
<textarea id="rtl-textarea" placeholder="اكتب تعليقًا..."></textarea>
<footer data-align="end">
<span>٠/٢٨٠</span>
<button type="button" class="btn ms-auto" data-size="sm">نشر</button>
</footer>
</div>
<p>تذييل موضع أسفل منطقة النص.</p>
</div>
</div>
</Preview>
+187
View File
@@ -0,0 +1,187 @@
# Input
<Preview class="w-full max-w-xs">
<input class="input" type="email" placeholder="Email" />
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Input component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/input.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your input HTML">
Add `class="input"` to supported `<input>` elements. Inputs inside a `class="field"` container also receive the same styling automatically for common text-like input types.
Use native HTML attributes for behavior: `disabled` for disabled inputs, `required` for required fields, and `aria-invalid="true"` for invalid state styling.
```html
<input class="input" type="email" placeholder="Email" />
```
</Step>
</Steps>
## Examples
### Basic
<Preview class="w-full max-w-xs">
<input class="input" type="email" placeholder="Email" />
</Preview>
### Field
Use `field` with a label and description to create an accessible field.
<Preview class="w-full max-w-xs">
<div role="group" class="field">
<label for="input-field">Email</label>
<input id="input-field" type="email" placeholder="m@example.com" />
<p>Enter your email address.</p>
</div>
</Preview>
### Field group
<Preview class="w-full max-w-xs">
<div class="grid gap-4">
<div role="group" class="field">
<label for="input-first-name">First name</label>
<input id="input-first-name" type="text" placeholder="Jane" />
</div>
<div role="group" class="field">
<label for="input-last-name">Last name</label>
<input id="input-last-name" type="text" placeholder="Doe" />
</div>
</div>
</Preview>
### Disabled
<Preview class="w-full max-w-xs">
<input class="input" type="email" placeholder="Email" disabled />
</Preview>
### Invalid
<Preview class="w-full max-w-xs">
<input class="input" type="email" placeholder="Email" aria-invalid="true" />
</Preview>
### File
<Preview class="w-full max-w-xs">
<input class="input" type="file" />
</Preview>
### Inline
<Preview class="w-full max-w-xs">
<div class="flex items-center gap-2">
<input class="input" type="search" placeholder="Search..." />
<button type="submit" class="btn">Search</button>
</div>
</Preview>
### Grid
<Preview class="w-full max-w-sm">
<div class="grid gap-4 md:grid-cols-2">
<input class="input" type="text" placeholder="First name" />
<input class="input" type="text" placeholder="Last name" />
<input class="input md:col-span-2" type="email" placeholder="Email" />
</div>
</Preview>
### Required
<Preview class="w-full max-w-xs">
<div role="group" class="field">
<label for="input-required">Username <span class="text-destructive">*</span></label>
<input id="input-required" type="text" placeholder="hunvreus" required />
</div>
</Preview>
### Badge
<Preview class="w-full max-w-xs">
<div role="group" class="field">
<label for="input-badge" class="flex w-full items-center gap-2">
Email
<span class="badge ml-auto" data-variant="secondary">Recommended</span>
</label>
<input id="input-badge" type="email" placeholder="m@example.com" />
</div>
</Preview>
### Input group
Use the [Input Group](/components/input-group/) patterns to add icons, text, or buttons inside an input.
<Preview class="w-full max-w-xs">
<div class="relative">
<input class="input ps-9" type="search" placeholder="Search..." />
<div class="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
</div>
</div>
</Preview>
### Button group
Use [Button Group](/components/button-group/) when an input should be grouped with adjacent buttons.
<Preview class="w-full max-w-xs">
<div role="group" aria-label="Search" class="button-group">
<input class="input" type="search" placeholder="Search..." />
<button type="submit" class="btn" data-variant="outline" data-size="icon" aria-label="Search"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></button>
</div>
</Preview>
### Form
<Preview class="w-full max-w-sm">
<form class="space-y-6">
<div role="group" class="field">
<label for="input-form-username">Username</label>
<input id="input-form-username" type="text" placeholder="hunvreus" required />
<p>This is your public display name.</p>
</div>
<div role="group" class="field">
<label for="input-form-email">Email</label>
<input id="input-form-email" type="email" placeholder="m@example.com" required />
</div>
<button type="submit" class="btn">Submit</button>
</form>
</Preview>
### RTL
Inputs support right-to-left layouts through native browser direction handling. Add `dir="rtl"` to the input, field, or an ancestor.
<Preview class="w-full max-w-xs">
<div role="group" class="field" dir="rtl">
<label for="input-rtl-api-key">مفتاح API</label>
<input id="input-rtl-api-key" type="password" placeholder="sk-..." />
<p>مفتاح API الخاص بك مشفر ومخزن بأمان.</p>
</div>
</Preview>
+374
View File
@@ -0,0 +1,374 @@
# Item
<Preview class="w-full max-w-md">
<div class="flex w-full flex-col gap-6">
<article class="item" data-variant="outline">
<section>
<h3>Basic Item</h3>
<p>A simple item with title and description.</p>
</section>
<aside>
<button class="btn" data-variant="outline" data-size="sm">Action</button>
</aside>
</article>
<a href="#" class="item" data-variant="outline" data-size="sm">
<figure><svg class="lucide lucide-badge-check size-5" 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"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></figure>
<section><h3>Your profile has been verified.</h3></section>
<aside><svg class="lucide lucide-chevron-right size-4" 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"><path d="m9 18 6-6-6-6"/></svg></aside>
</a>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, the component CSS files it composes, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/item.css";
@import "basecoat-css/components/button.css";
@import "basecoat-css/styles/vega.css";
```
Item composes Button styles when actions are rendered as buttons.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your item HTML">
Add `class="item"` to a semantic element such as `<article>` or `<a>`. Use `class="item-group"` when listing multiple items.
```html
<article class="item" data-variant="outline">
<section>
<h3>Basic Item</h3>
<p>A simple item with title and description.</p>
</section>
<aside>
<button class="btn" data-variant="outline" data-size="sm">Action</button>
</aside>
</article>
```
</Step>
</Steps>
#### HTML structure
<dl>
<dt><code>&lt;article class="item"&gt;</code> or <code>&lt;a class="item"&gt;</code></dt>
<dd>
The item root. Use <code>data-variant="outline|muted"</code> and <code>data-size="sm|xs"</code> when needed.
<dl>
<dt><code>&lt;figure&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Media: icon, avatar, or image.</dd>
<dt><code>&lt;section&gt;</code></dt>
<dd>
Primary content. Headings and paragraphs are styled automatically.
<dl>
<dt><code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>, or <code>&lt;h4&gt;</code></dt>
<dd>The item title.</dd>
<dt><code>&lt;p&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The item description.</dd>
</dl>
</dd>
<dt><code>&lt;aside&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Actions or trailing metadata.</dd>
<dt><code>&lt;header&gt;</code>, <code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Full-width item regions.</dd>
</dl>
</dd>
<dt><code>&lt;div class="item-group" role="list"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Groups related items. Use <code>role="listitem"</code> on child items when they are not native list items.</dd>
</dl>
### Item vs Field
Use `field` for form controls and validation. Use `item` for display content, actions, and navigation rows.
### Variant
<Preview class="w-full max-w-md">
<div class="flex w-full flex-col gap-6">
<article class="item">
<figure><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg></figure>
<section>
<h3>Default Variant</h3>
<p>Transparent background with no border.</p>
</section>
</article>
<article class="item" data-variant="outline">
<figure><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg></figure>
<section>
<h3>Outline Variant</h3>
<p>Outlined style with a visible border.</p>
</section>
</article>
<article class="item" data-variant="muted">
<figure><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg></figure>
<section>
<h3>Muted Variant</h3>
<p>Muted background for secondary content.</p>
</section>
</article>
</div>
</Preview>
### Size
<Preview class="w-full max-w-md">
<div class="flex w-full flex-col gap-6">
<article class="item" data-variant="outline">
<figure><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg></figure>
<section>
<h3>Default Size</h3>
<p>The standard size for most use cases.</p>
</section>
</article>
<article class="item" data-variant="outline" data-size="sm">
<figure><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg></figure>
<section>
<h3>Small Size</h3>
<p>A compact size for dense layouts.</p>
</section>
</article>
<article class="item" data-variant="outline" data-size="xs">
<figure><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg></figure>
<section>
<h3>Extra Small Size</h3>
<p>The most compact size available.</p>
</section>
</article>
</div>
</Preview>
## Examples
### Icon
<Preview class="w-full max-w-lg">
<div class="flex w-full flex-col gap-6">
<article class="item" data-variant="outline">
<figure><svg class="lucide lucide-shield-alert" 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"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg></figure>
<section>
<h3>Security Alert</h3>
<p>New login detected from unknown device.</p>
</section>
<aside><button class="btn" data-variant="outline" data-size="sm">Review</button></aside>
</article>
</div>
</Preview>
### Avatar
<Preview class="w-full max-w-lg">
<div class="flex w-full flex-col gap-6">
<article class="item" data-variant="outline">
<figure>
<span class="avatar" data-size="lg"><img src="https://github.com/evilrabbit.png" alt="Evil Rabbit" /></span>
</figure>
<section>
<h3>Evil Rabbit</h3>
<p>Last seen 5 months ago</p>
</section>
<aside>
<button class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Invite Evil Rabbit"><svg class="lucide lucide-plus" 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"><path d="M5 12h14"/><path d="M12 5v14"/></svg></button>
</aside>
</article>
<article class="item" data-variant="outline">
<figure>
<span class="avatar-group">
<span class="avatar hidden sm:flex"><img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" /></span>
<span class="avatar hidden sm:flex"><img class="grayscale" src="https://github.com/maxleiter.png" alt="@maxleiter" /></span>
<span class="avatar"><img class="grayscale" src="https://github.com/evilrabbit.png" alt="@evilrabbit" /></span>
</span>
</figure>
<section>
<h3>No Team Members</h3>
<p>Invite your team to collaborate on this project.</p>
</section>
<aside><button class="btn" data-variant="outline" data-size="sm">Invite</button></aside>
</article>
</div>
</Preview>
### Image
<Preview class="w-full max-w-md">
<div class="item-group gap-4" role="list">
<a href="#" class="item" data-variant="outline" role="listitem">
<figure><img class="grayscale" src="https://avatar.vercel.sh/Midnight City Lights" alt="Midnight City Lights" /></figure>
<section><h3>Midnight City Lights - <span class="text-muted-foreground">Electric Nights</span></h3><p>Neon Dreams</p></section>
<section><p>3:45</p></section>
</a>
<a href="#" class="item" data-variant="outline" role="listitem">
<figure><img class="grayscale" src="https://avatar.vercel.sh/Coffee Shop Conversations" alt="Coffee Shop Conversations" /></figure>
<section><h3>Coffee Shop Conversations - <span class="text-muted-foreground">Urban Stories</span></h3><p>The Morning Brew</p></section>
<section><p>4:05</p></section>
</a>
<a href="#" class="item" data-variant="outline" role="listitem">
<figure><img class="grayscale" src="https://avatar.vercel.sh/Digital Rain" alt="Digital Rain" /></figure>
<section><h3>Digital Rain - <span class="text-muted-foreground">Binary Beats</span></h3><p>Cyber Symphony</p></section>
<section><p>3:30</p></section>
</a>
</div>
</Preview>
### Group
<Preview class="w-full max-w-md">
<div class="flex w-full flex-col gap-6">
<div class="item-group" role="list">
<article class="item" role="listitem">
<figure><span class="avatar"><img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" /></span></figure>
<section><h3>shadcn</h3><p>shadcn@vercel.com</p></section>
<aside>
<button class="btn rounded-full" data-variant="ghost" data-size="icon" aria-label="Add shadcn"><svg class="lucide lucide-plus" 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"><path d="M5 12h14"/><path d="M12 5v14"/></svg></button>
</aside>
</article>
<article class="item" role="listitem">
<figure><span class="avatar"><img class="grayscale" src="https://github.com/maxleiter.png" alt="@maxleiter" /></span></figure>
<section><h3>maxleiter</h3><p>maxleiter@vercel.com</p></section>
<aside>
<button class="btn rounded-full" data-variant="ghost" data-size="icon" aria-label="Add maxleiter"><svg class="lucide lucide-plus" 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"><path d="M5 12h14"/><path d="M12 5v14"/></svg></button>
</aside>
</article>
<article class="item" role="listitem">
<figure><span class="avatar"><img class="grayscale" src="https://github.com/evilrabbit.png" alt="@evilrabbit" /></span></figure>
<section><h3>evilrabbit</h3><p>evilrabbit@vercel.com</p></section>
<aside>
<button class="btn rounded-full" data-variant="ghost" data-size="icon" aria-label="Add evilrabbit"><svg class="lucide lucide-plus" 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"><path d="M5 12h14"/><path d="M12 5v14"/></svg></button>
</aside>
</article>
</div>
</div>
</Preview>
### Header
<Preview class="w-full max-w-xl">
<div class="item-group grid grid-cols-1 gap-4 sm:grid-cols-3" role="list">
<article class="item" data-variant="outline" role="listitem">
<header><img class="aspect-square w-full rounded-sm object-cover grayscale" src="https://images.unsplash.com/photo-1650804068570-7fb2e3dbf888?q=80&w=640&auto=format&fit=crop" alt="v0-1.5-sm" /></header>
<section><h3>v0-1.5-sm</h3><p>Everyday tasks and UI generation.</p></section>
</article>
<article class="item" data-variant="outline" role="listitem">
<header><img class="aspect-square w-full rounded-sm object-cover grayscale" src="https://images.unsplash.com/photo-1610280777472-54133d004c8c?q=80&w=640&auto=format&fit=crop" alt="v0-1.5-lg" /></header>
<section><h3>v0-1.5-lg</h3><p>Advanced thinking or reasoning.</p></section>
</article>
<article class="item" data-variant="outline" role="listitem">
<header><img class="aspect-square w-full rounded-sm object-cover grayscale" src="https://images.unsplash.com/photo-1602146057681-08560aee8cde?q=80&w=640&auto=format&fit=crop" alt="v0-2.0-mini" /></header>
<section><h3>v0-2.0-mini</h3><p>Open Source model for everyone.</p></section>
</article>
</div>
</Preview>
### Link
<Preview class="w-full max-w-md">
<div class="flex w-full flex-col gap-4">
<a href="#" class="item">
<section>
<h3>Visit our documentation</h3>
<p>Learn how to get started with our components.</p>
</section>
<aside><svg class="lucide lucide-chevron-right size-4" 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"><path d="m9 18 6-6-6-6"/></svg></aside>
</a>
<a href="#" target="_blank" rel="noopener noreferrer" class="item" data-variant="outline">
<section>
<h3>External resource</h3>
<p>Opens in a new tab with security attributes.</p>
</section>
<aside><svg class="lucide lucide-external-link size-4" 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"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg></aside>
</a>
</div>
</Preview>
### Dropdown
<Preview class="w-full max-w-md">
<div class="flex min-h-64 w-full items-center justify-center">
<div id="item-dropdown" class="dropdown-menu">
<button type="button" id="item-dropdown-trigger" aria-haspopup="menu" aria-controls="item-dropdown-menu" aria-expanded="false" class="btn w-fit" data-variant="outline">
Select
<svg class="lucide lucide-chevron-down" data-icon="inline-end" 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"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div id="item-dropdown-popover" data-popover data-align="end" aria-hidden="true" class="w-48">
<div role="menu" id="item-dropdown-menu" aria-labelledby="item-dropdown-trigger">
<button type="button" role="menuitem">
<span class="item border-0 p-0 shadow-none" data-size="xs">
<figure>
<span class="avatar" data-size="sm">
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
</span>
</figure>
<section class="gap-0">
<h3>shadcn</h3>
<p class="leading-none" aria-label="shadcn@vercel.com">shadcn<span aria-hidden="true">@</span>vercel.com</p>
</section>
</span>
</button>
<button type="button" role="menuitem">
<span class="item border-0 p-0 shadow-none" data-size="xs">
<figure>
<span class="avatar" data-size="sm">
<img class="grayscale" src="https://github.com/maxleiter.png" alt="@maxleiter" />
</span>
</figure>
<section class="gap-0">
<h3>maxleiter</h3>
<p class="leading-none" aria-label="maxleiter@vercel.com">maxleiter<span aria-hidden="true">@</span>vercel.com</p>
</section>
</span>
</button>
<button type="button" role="menuitem">
<span class="item border-0 p-0 shadow-none" data-size="xs">
<figure>
<span class="avatar" data-size="sm">
<img class="grayscale" src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
</span>
</figure>
<section class="gap-0">
<h3>evilrabbit</h3>
<p class="leading-none" aria-label="evilrabbit@vercel.com">evilrabbit<span aria-hidden="true">@</span>vercel.com</p>
</section>
</span>
</button>
</div>
</div>
</div>
</div>
</Preview>
### RTL
<Preview class="w-full max-w-md">
<div class="flex w-full flex-col gap-6" dir="rtl">
<article class="item" data-variant="outline">
<section>
<h3>عنصر أساسي</h3>
<p>عنصر بسيط يحتوي على عنوان ووصف.</p>
</section>
<aside>
<button class="btn" data-variant="outline" data-size="sm">إجراء</button>
</aside>
</article>
<a href="#" class="item" data-variant="outline" data-size="sm">
<figure><svg class="lucide lucide-badge-check size-5" 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"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></figure>
<section><h3>تم التحقق من ملفك الشخصي.</h3></section>
<aside><svg class="lucide lucide-chevron-right size-4 rtl:rotate-180" 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"><path d="m9 18 6-6-6-6"/></svg></aside>
</a>
</div>
</Preview>
+110
View File
@@ -0,0 +1,110 @@
# Kbd
<Preview>
<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>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Kbd component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/button.css";
@import "basecoat-css/components/button-group.css";
@import "basecoat-css/components/input-group.css";
@import "basecoat-css/components/kbd.css";
@import "basecoat-css/components/tooltip.css";
@import "basecoat-css/styles/vega.css";
```
Kbd examples compose Button, Button Group, Input Group, and Tooltip styles.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your kbd HTML">
Use `<kbd class="kbd">` for a key label. Use an inline wrapper with utilities when grouping multiple keys.
```html
<kbd class="kbd">⌘K</kbd>
```
</Step>
</Steps>
Keyboard labels are direction-neutral. Surrounding spacing should use logical utilities when position matters.
## Examples
### Group
<Preview>
<p class="text-muted-foreground text-sm">Use <span class="inline-flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><span>+</span><kbd class="kbd">B</kbd></span> <span class="inline-flex items-center gap-1"><kbd class="kbd">Ctrl</kbd><span>+</span><kbd class="kbd">K</kbd></span> to open the command palette</p>
</Preview>
### Button
<Preview>
<button class="btn" data-variant="outline">
Accept
<kbd class="kbd translate-x-0.5" data-icon="inline-end">⏎</kbd>
</button>
</Preview>
### Input Group
<Preview class="w-full max-w-xs">
<div class="input-group">
<input type="search" placeholder="Search..." />
<span data-align="start" aria-hidden="true"><svg class="lucide lucide-search" 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"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg></span>
<span class="inline-flex items-center gap-1" data-align="end">
<kbd class="kbd">⌘</kbd>
<kbd class="kbd">K</kbd>
</span>
</div>
</Preview>
## RTL
Keyboard labels are direction-neutral. Surrounding spacing should use logical utilities when position matters.
<Preview>
<div class="flex flex-col items-center gap-4" dir="rtl">
<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>
</Preview>
+56
View File
@@ -0,0 +1,56 @@
# Label
<Preview>
<label class="label gap-2">
<input type="checkbox" class="input" />
Accept terms and conditions
</label>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Label component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/label.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your label HTML">
Add `class="label"` to a `<label>` element.
```html
<label class="label" for="email">Your email address</label>
```
</Step>
</Steps>
## Examples
### RTL
Add `dir="rtl"` to the label or an ancestor.
<Preview>
<div class="grid gap-3" dir="rtl">
<label class="label" for="email-rtl">البريد الإلكتروني</label>
<input class="input" id="email-rtl" type="email" placeholder="name@example.com" />
</div>
</Preview>
+128
View File
@@ -0,0 +1,128 @@
# Native Select
<Preview>
<select class="select w-full" aria-label="Fruit">
<option value="">Select a fruit</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="blueberry">Blueberry</option>
<option value="pineapple">Pineapple</option>
</select>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Native Select component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/native-select.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your native select HTML">
Add `class="select"` to a native `<select>`. Use standard `<option>` and `<optgroup>` elements.
```html
<select class="select" aria-label="Fruit">
<option value="">Select a fruit</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="blueberry">Blueberry</option>
<option value="pineapple">Pineapple</option>
</select>
```
</Step>
</Steps>
Use Native Select for native browser behavior, form integration, and mobile-optimized pickers. Use [Select](/components/select/) for custom option content, multi-select behavior, or JavaScript APIs.
## Examples
### Groups
<Preview>
<select class="select w-full" aria-label="Food">
<option value="">Select food</option>
<optgroup label="Fruits">
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="blueberry">Blueberry</option>
</optgroup>
<optgroup label="Vegetables">
<option value="carrot">Carrot</option>
<option value="potato">Potato</option>
<option value="tomato">Tomato</option>
</optgroup>
</select>
</Preview>
### Disabled
<Preview>
<select class="select w-full" aria-label="Fruit" disabled>
<option value="">Select a fruit</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
</select>
</Preview>
### Invalid
<Preview>
<div role="group" class="field" data-invalid="true">
<label for="native-select-invalid">Fruit</label>
<select id="native-select-invalid" class="select w-full" aria-invalid="true" aria-describedby="native-select-invalid-error">
<option value="">Select a fruit</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
</select>
<p id="native-select-invalid-error" role="alert">Select a fruit to continue.</p>
</div>
</Preview>
### Size
<Preview>
<div class="flex flex-wrap items-center gap-3">
<select class="select w-42" aria-label="Default size">
<option>Default</option>
<option>Apple</option>
<option>Banana</option>
</select>
<select class="select w-42" data-size="sm" aria-label="Small size">
<option>Small</option>
<option>Apple</option>
<option>Banana</option>
</select>
</div>
</Preview>
### RTL
Set `dir="rtl"` on the select or a parent element.
<Preview>
<select dir="rtl" class="select w-full" aria-label="الفاكهة">
<option value="">اختر فاكهة</option>
<option value="apple">تفاح</option>
<option value="banana">موز</option>
<option value="blueberry">توت</option>
</select>
</Preview>
+113
View File
@@ -0,0 +1,113 @@
# Pagination
<Callout title="No dedicated component" icon="circle-alert">
There is no dedicated pagination component in Basecoat.
</Callout>
<Preview class="max-w-md">
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
<ul class="flex flex-row items-center gap-1">
<li>
<a href="#" class="btn" data-variant="ghost">
<svg class="lucide lucide-chevron-left" 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"><path d="m15 18-6-6 6-6" /></svg>
<span>Previous</span>
</a>
</li>
<li><a href="#" class="btn" data-variant="ghost" data-size="icon">1</a></li>
<li><a href="#" class="btn" data-variant="outline" data-size="icon">2</a></li>
<li><a href="#" class="btn" data-variant="ghost" data-size="icon">3</a></li>
<li><div class="size-9 flex items-center justify-center"><svg class="size-4 shrink-0 lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></div></li>
<li>
<a href="#" class="btn" data-variant="ghost">
<span>Next</span>
<svg class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg>
</a>
</li>
</ul>
</nav>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Pagination component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/button.css";
@import "basecoat-css/styles/vega.css";
```
Pagination uses Button component CSS for its links and controls.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your pagination HTML">
A pagination is fairly simple to implement with Tailwind using the Basecoat button classes:
```html
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
<ul class="flex flex-row items-center gap-1">
<li>
<a href="#" class="btn" data-variant="ghost">
<svg class="lucide lucide-chevron-left" 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"><path d="m15 18-6-6 6-6" /></svg>
Previous
</a>
</li>
<li><a href="#" class="btn" data-variant="ghost" data-size="icon">1</a></li>
<li><a href="#" class="btn" data-variant="outline" data-size="icon">2</a></li>
<li><a href="#" class="btn" data-variant="ghost" data-size="icon">3</a></li>
<li><div class="size-9 flex items-center justify-center"><svg class="size-4 shrink-0 lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></div></li>
<li>
<a href="#" class="btn" data-variant="ghost">
Next
<svg class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg>
</a>
</li>
</ul>
</nav>
```
</Step>
</Steps>
## Examples
### RTL
Pagination uses document direction and logical spacing. Set `dir="rtl"` on the pagination root or a parent.
<Preview class="max-w-md">
<nav dir="rtl" role="navigation" aria-label="ترقيم الصفحات" class="mx-auto flex w-full justify-center">
<ul class="flex flex-row items-center gap-1">
<li>
<a href="#" class="btn" data-variant="ghost">
<svg class="lucide lucide-chevron-right" 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"><path d="m9 18 6-6-6-6" /></svg>
<span>السابق</span>
</a>
</li>
<li><a href="#" class="btn" data-variant="ghost" data-size="icon">١</a></li>
<li><a href="#" class="btn" data-variant="outline" data-size="icon">٢</a></li>
<li><a href="#" class="btn" data-variant="ghost" data-size="icon">٣</a></li>
<li><div class="size-9 flex items-center justify-center"><svg class="size-4 shrink-0 lucide lucide-ellipsis" 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"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg></div></li>
<li>
<a href="#" class="btn" data-variant="ghost">
<span>التالي</span>
<svg class="lucide lucide-chevron-left" 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"><path d="m15 18-6-6 6-6" /></svg>
</a>
</li>
</ul>
</nav>
</Preview>
+263
View File
@@ -0,0 +1,263 @@
# Popover
<Preview>
<div id="demo-popover" class="popover">
<button id="demo-popover-trigger" type="button" aria-expanded="false" aria-controls="demo-popover-popover" class="btn" data-variant="outline">Open popover</button>
<div id="demo-popover-popover" data-popover aria-hidden="true" class="w-72">
<header>
<h4>Dimensions</h4>
<p>Set the dimensions for the layer.</p>
</header>
<form class="grid gap-2" onsubmit="event.preventDefault()">
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-width">Width</label>
<input type="text" id="demo-popover-width" value="100%" class="input col-span-2 h-8" autofocus />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-max-width">Max. width</label>
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-height">Height</label>
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-max-height">Max. height</label>
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8" />
</div>
</form>
</div>
</div>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#popover" }}>
This component ships a `popover()` macro for Jinja and Nunjucks.
</Callout>
Basecoat popovers are inline-positioned relative to the `.popover` wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and works with Basecoat's existing dropdown/select positioning model.
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Popover component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/popover.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Popover script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/popover.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your popover HTML">
```html
<div id="demo-popover" class="popover">
<button id="demo-popover-trigger" type="button" aria-expanded="false" aria-controls="demo-popover-popover" class="btn" data-variant="outline">Open popover</button>
<div id="demo-popover-popover" data-popover aria-hidden="true" class="w-72">
<header>
<h4>Dimensions</h4>
<p>Set the dimensions for the layer.</p>
</header>
<form class="grid gap-2" onsubmit="event.preventDefault()">
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-width">Width</label>
<input type="text" id="demo-popover-width" value="100%" class="input col-span-2 h-8" autofocus />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-max-width">Max. width</label>
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-height">Height</label>
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="demo-popover-max-height">Max. height</label>
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8" />
</div>
</form>
</div>
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="popover"&gt;</code></dt>
<dd>
Relative wrapper for the trigger and inline popover content.
<dl>
<dt><code>&lt;button type="button" aria-expanded="false" aria-controls="&#123; POPOVER_ID &#125;"&gt;</code></dt>
<dd>Trigger button. The script toggles <code>aria-expanded</code> and opens or closes the content.</dd>
<dt><code>&lt;div data-popover id="&#123; POPOVER_ID &#125;" aria-hidden="true"&gt;</code></dt>
<dd>Popover content. Supports <code>data-side="top|right|bottom|left|inline-start|inline-end"</code> and <code>data-align="start|center|end"</code>.</dd>
</dl>
</dd>
</dl>
## Examples
### Basic
<Preview>
<div id="popover-basic" class="popover">
<button id="popover-basic-trigger" type="button" aria-expanded="false" aria-controls="popover-basic-popover" class="btn" data-variant="outline">Open</button>
<div id="popover-basic-popover" data-popover aria-hidden="true" class="w-72">
<header>
<h4>Title</h4>
<p>Description text here.</p>
</header>
</div>
</div>
</Preview>
### Align
<Preview>
<div class="flex flex-wrap gap-3">
<div id="popover-align-start" class="popover">
<button id="popover-align-start-trigger" type="button" aria-expanded="false" aria-controls="popover-align-start-popover" class="btn" data-variant="outline">Start</button>
<div id="popover-align-start-popover" data-popover aria-hidden="true" class="w-56" data-align="start">
<p>Aligned to start.</p>
</div>
</div>
<div id="popover-align-center" class="popover">
<button id="popover-align-center-trigger" type="button" aria-expanded="false" aria-controls="popover-align-center-popover" class="btn" data-variant="outline">Center</button>
<div id="popover-align-center-popover" data-popover aria-hidden="true" class="w-56" data-align="center">
<p>Aligned to center.</p>
</div>
</div>
<div id="popover-align-end" class="popover">
<button id="popover-align-end-trigger" type="button" aria-expanded="false" aria-controls="popover-align-end-popover" class="btn" data-variant="outline">End</button>
<div id="popover-align-end-popover" data-popover aria-hidden="true" class="w-56" data-align="end">
<p>Aligned to end.</p>
</div>
</div>
</div>
</Preview>
### Sides
<Preview>
<div class="flex flex-wrap gap-3">
<div id="popover-side-inline-start" class="popover">
<button id="popover-side-inline-start-trigger" type="button" aria-expanded="false" aria-controls="popover-side-inline-start-popover" class="btn" data-variant="outline">Inline start</button>
<div id="popover-side-inline-start-popover" data-popover aria-hidden="true" data-side="inline-start" data-align="center" class="w-40">
<p>Inline start side.</p>
</div>
</div>
<div id="popover-side-top" class="popover">
<button id="popover-side-top-trigger" type="button" aria-expanded="false" aria-controls="popover-side-top-popover" class="btn" data-variant="outline">Top</button>
<div id="popover-side-top-popover" data-popover aria-hidden="true" data-side="top" data-align="center" class="w-40">
<p>Top side.</p>
</div>
</div>
<div id="popover-side-right" class="popover">
<button id="popover-side-right-trigger" type="button" aria-expanded="false" aria-controls="popover-side-right-popover" class="btn" data-variant="outline">Right</button>
<div id="popover-side-right-popover" data-popover aria-hidden="true" data-side="right" data-align="center" class="w-40">
<p>Right side.</p>
</div>
</div>
<div id="popover-side-bottom" class="popover">
<button id="popover-side-bottom-trigger" type="button" aria-expanded="false" aria-controls="popover-side-bottom-popover" class="btn" data-variant="outline">Bottom</button>
<div id="popover-side-bottom-popover" data-popover aria-hidden="true" data-side="bottom" data-align="center" class="w-40">
<p>Bottom side.</p>
</div>
</div>
<div id="popover-side-left" class="popover">
<button id="popover-side-left-trigger" type="button" aria-expanded="false" aria-controls="popover-side-left-popover" class="btn" data-variant="outline">Left</button>
<div id="popover-side-left-popover" data-popover aria-hidden="true" data-side="left" data-align="center" class="w-40">
<p>Left side.</p>
</div>
</div>
<div id="popover-side-inline-end" class="popover">
<button id="popover-side-inline-end-trigger" type="button" aria-expanded="false" aria-controls="popover-side-inline-end-popover" class="btn" data-variant="outline">Inline end</button>
<div id="popover-side-inline-end-popover" data-popover aria-hidden="true" data-side="inline-end" data-align="center" class="w-40">
<p>Inline end side.</p>
</div>
</div>
</div>
</Preview>
### With form
<Preview>
<div id="popover-form" class="popover">
<button id="popover-form-trigger" type="button" aria-expanded="false" aria-controls="popover-form-popover" class="btn" data-variant="outline">Open popover</button>
<div id="popover-form-popover" data-popover aria-hidden="true" class="w-72">
<header>
<h4>Dimensions</h4>
<p>Set the dimensions for the layer.</p>
</header>
<form class="grid gap-2" onsubmit="event.preventDefault()">
<div class="grid grid-cols-3 items-center gap-4">
<label for="popover-form-width">Width</label>
<input type="text" id="popover-form-width" value="100%" class="input col-span-2 h-8" autofocus />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="popover-form-max-width">Max. width</label>
<input type="text" id="popover-form-max-width" value="300px" class="input col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="popover-form-height">Height</label>
<input type="text" id="popover-form-height" value="25px" class="input col-span-2 h-8" />
</div>
<div class="grid grid-cols-3 items-center gap-4">
<label for="popover-form-max-height">Max. height</label>
<input type="text" id="popover-form-max-height" value="none" class="input col-span-2 h-8" />
</div>
</form>
</div>
</div>
</Preview>
### RTL
Popover alignment uses logical start and end offsets. Set `dir="rtl"` on a parent to flip start/end alignment.
<Preview>
<div dir="rtl">
<div id="popover-rtl" class="popover">
<button id="popover-rtl-trigger" type="button" aria-expanded="false" aria-controls="popover-rtl-popover" class="btn" data-variant="outline">فتح</button>
<div id="popover-rtl-popover" data-popover aria-hidden="true" class="w-72" data-align="start">
<header>
<h4>العنوان</h4>
<p>نص الوصف هنا.</p>
</header>
</div>
</div>
</div>
</Preview>
+91
View File
@@ -0,0 +1,91 @@
# Progress
<Preview class="w-full max-w-sm">
<div id="progress-demo" class="progress" role="progressbar" aria-label="Loading" aria-valuenow="13" aria-valuemin="0" aria-valuemax="100">
<span style="width: 13%"></span>
</div>
<script>{`
setTimeout(() => {
const progress = document.getElementById("progress-demo");
const indicator = progress?.querySelector("span");
if (!progress || !indicator) return;
progress.setAttribute("aria-valuenow", "66");
indicator.style.width = "66%";
}, 500);
`}</script>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Progress component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/progress.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your progress HTML">
Add `class="progress"` and `role="progressbar"` to the root. Use one child `<span>` as the visual indicator.
```html
<div class="progress" role="progressbar" aria-label="Loading" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<span style="width: 66%"></span>
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="progress" role="progressbar"&gt;</code></dt>
<dd>Progress root. Set <code>aria-label</code> or <code>aria-labelledby</code>, plus <code>aria-valuenow</code>, <code>aria-valuemin</code>, and <code>aria-valuemax</code>.</dd>
<dt><code>&lt;span style="width: ..."&gt;</code></dt>
<dd>Visual indicator. Its width should match the current value.</dd>
</dl>
## Examples
### Label
<Preview class="w-full max-w-sm">
<div class="field">
<div class="flex items-center gap-3 text-sm">
<label id="progress-label">Upload progress</label>
<output class="ms-auto tabular-nums">66%</output>
</div>
<div class="progress" role="progressbar" aria-labelledby="progress-label" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<span style="width: 66%"></span>
</div>
</div>
</Preview>
### RTL
Progress supports `dir="rtl"` on the root or a parent.
<Preview class="w-full max-w-sm">
<div class="field w-full" dir="rtl">
<label id="progress-upload-rtl" for="progress-upload">تقدم الرفع <span class="ms-auto">٦٦%</span></label>
<div id="progress-upload" class="progress" role="progressbar" aria-labelledby="progress-upload-rtl" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<span style="width: 66%"></span>
</div>
</div>
</Preview>
+225
View File
@@ -0,0 +1,225 @@
# Radio Group
<Preview>
<div role="radiogroup" aria-label="View density" data-slot="radio-group" class="w-fit">
<div class="flex items-center gap-3">
<input type="radio" id="r1" name="radio-demo" value="default" class="input" />
<label for="r1">Default</label>
</div>
<div class="flex items-center gap-3">
<input type="radio" id="r2" name="radio-demo" value="comfortable" class="input" checked />
<label for="r2">Comfortable</label>
</div>
<div class="flex items-center gap-3">
<input type="radio" id="r3" name="radio-demo" value="compact" class="input" />
<label for="r3">Compact</label>
</div>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Radio Group component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/field.css";
@import "basecoat-css/components/radio.css";
@import "basecoat-css/styles/vega.css";
```
Radio Group uses Radio styles and composes Field styles in the richer examples.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your radio group HTML">
Add `class="input"` to radio inputs. Use a shared `name` attribute for mutually exclusive options, and wrap related options in a semantic `<fieldset>` or an element with `role="radiogroup"`.
```html
<input type="radio" name="plan" class="input" />
```
</Step>
</Steps>
## Examples
### Description
Radio group items with a description using the `Field` component.
<Preview>
<div role="radiogroup" aria-label="View density" data-slot="radio-group" class="w-fit">
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="desc-r1" name="radio-description" value="default" class="input" />
<section>
<label for="desc-r1">Default</label>
<p>Standard spacing for most use cases.</p>
</section>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="desc-r2" name="radio-description" value="comfortable" class="input" checked />
<section>
<label for="desc-r2">Comfortable</label>
<p>More space between elements.</p>
</section>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="desc-r3" name="radio-description" value="compact" class="input" />
<section>
<label for="desc-r3">Compact</label>
<p>Minimal spacing for dense layouts.</p>
</section>
</div>
</div>
</Preview>
### Choice Card
Use a native `<label>` to wrap the entire `.field` for a clickable card-style selection.
<Preview class="w-full max-w-sm">
<div role="radiogroup" aria-label="Plans" data-slot="radio-group" class="w-full max-w-md">
<label for="plus-plan">
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Plus</h3>
<p>For individuals and small teams.</p>
</section>
<input type="radio" id="plus-plan" name="choice-plan" value="plus" class="input" checked />
</div>
</label>
<label for="pro-plan">
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Pro</h3>
<p>For growing businesses.</p>
</section>
<input type="radio" id="pro-plan" name="choice-plan" value="pro" class="input" />
</div>
</label>
<label for="enterprise-plan">
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Enterprise</h3>
<p>For large teams and enterprises.</p>
</section>
<input type="radio" id="enterprise-plan" name="choice-plan" value="enterprise" class="input" />
</div>
</label>
</div>
</Preview>
### Fieldset
Use a native `<fieldset>` and `<legend>` to group radio items with a label and description.
<Preview>
<fieldset class="fieldset">
<legend data-variant="label">Subscription Plan</legend>
<p>Yearly and lifetime plans offer significant savings.</p>
<div role="radiogroup" aria-label="Subscription plan" data-slot="radio-group">
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="plan-monthly" name="subscription-plan" value="monthly" class="input" checked />
<label for="plan-monthly" class="font-normal">Monthly ($9.99/month)</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="plan-yearly" name="subscription-plan" value="yearly" class="input" />
<label for="plan-yearly" class="font-normal">Yearly ($99.99/year)</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="plan-lifetime" name="subscription-plan" value="lifetime" class="input" />
<label for="plan-lifetime" class="font-normal">Lifetime ($299.99)</label>
</div>
</div>
</fieldset>
</Preview>
### Disabled
Use the `disabled` attribute to disable individual items.
<Preview>
<div role="radiogroup" aria-label="Disabled options" data-slot="radio-group" class="w-fit">
<div role="group" class="field" data-orientation="horizontal" data-disabled>
<input type="radio" id="disabled-1" name="radio-disabled" value="option1" class="input" disabled />
<label for="disabled-1" class="font-normal">Disabled</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="disabled-2" name="radio-disabled" value="option2" class="input" checked />
<label for="disabled-2" class="font-normal">Option 2</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="disabled-3" name="radio-disabled" value="option3" class="input" />
<label for="disabled-3" class="font-normal">Option 3</label>
</div>
</div>
</Preview>
### Invalid
Use `aria-invalid="true"` on radio inputs and `data-invalid` on fields to show validation errors.
<Preview>
<fieldset class="fieldset">
<legend data-variant="label">Notification Preferences</legend>
<p>Choose how you want to receive notifications.</p>
<div role="radiogroup" aria-label="Notification preferences" data-slot="radio-group">
<div role="group" class="field" data-orientation="horizontal" data-invalid>
<input type="radio" id="invalid-email" name="notification-preference" value="email" class="input" aria-invalid="true" checked />
<label for="invalid-email" class="font-normal">Email only</label>
</div>
<div role="group" class="field" data-orientation="horizontal" data-invalid>
<input type="radio" id="invalid-sms" name="notification-preference" value="sms" class="input" aria-invalid="true" />
<label for="invalid-sms" class="font-normal">SMS only</label>
</div>
<div role="group" class="field" data-orientation="horizontal" data-invalid>
<input type="radio" id="invalid-both" name="notification-preference" value="both" class="input" aria-invalid="true" />
<label for="invalid-both" class="font-normal">Both Email & SMS</label>
</div>
</div>
</fieldset>
</Preview>
## RTL
Set `dir="rtl"` on the radio group or an ancestor.
<Preview>
<div dir="rtl" role="radiogroup" aria-label="كثافة العرض" data-slot="radio-group" class="w-fit">
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="r1-rtl" name="radio-rtl" value="default" class="input" />
<section>
<label for="r1-rtl">افتراضي</label>
<p>تباعد قياسي لمعظم حالات الاستخدام.</p>
</section>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="r2-rtl" name="radio-rtl" value="comfortable" class="input" checked />
<section>
<label for="r2-rtl">مريح</label>
<p>مساحة أكبر بين العناصر.</p>
</section>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="radio" id="r3-rtl" name="radio-rtl" value="compact" class="input" />
<section>
<label for="r3-rtl">مضغوط</label>
<p>تباعد أدنى للتخطيطات الكثيفة.</p>
</section>
</div>
</div>
</Preview>
+166
View File
@@ -0,0 +1,166 @@
# Scroll Area
<Preview class="w-full max-w-42">
<div class="card h-72 p-0">
<div class="scrollbar h-full overflow-y-auto p-4">
<div class="space-y-4">
<h4 class="text-sm leading-none font-medium">Tags</h4>
<div class="text-sm">
<div class="border-b py-2">v1.2.0-beta.50</div>
<div class="border-b py-2">v1.2.0-beta.49</div>
<div class="border-b py-2">v1.2.0-beta.48</div>
<div class="border-b py-2">v1.2.0-beta.47</div>
<div class="border-b py-2">v1.2.0-beta.46</div>
<div class="border-b py-2">v1.2.0-beta.45</div>
<div class="border-b py-2">v1.2.0-beta.44</div>
<div class="border-b py-2">v1.2.0-beta.43</div>
<div class="border-b py-2">v1.2.0-beta.42</div>
<div class="border-b py-2">v1.2.0-beta.41</div>
<div class="border-b py-2">v1.2.0-beta.40</div>
<div class="border-b py-2">v1.2.0-beta.39</div>
<div class="py-2">v1.2.0-beta.38</div>
</div>
</div>
</div>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/styles/vega.css";
```
Scroll Area uses Basecoat scrollbar component classes.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your scroll area HTML">
Use `scrollbar` or `scrollbar-sm` on an element that already scrolls with `overflow-auto`, `overflow-y-auto`, or `overflow-x-auto`.
```html
<div class="scrollbar h-72 overflow-y-auto rounded-md border p-4">
Your scrollable content here.
</div>
```
</Step>
</Steps>
Use `scrollbar` or `scrollbar-sm` on an element that already scrolls with `overflow-auto`, `overflow-y-auto`, or `overflow-x-auto`.
The component class augments native scrolling for cross-browser styling. It does not wrap content, replace browser scroll behavior, or require JavaScript.
## Examples
These examples use Card as the framed surface so border and radius come from the active style pack.
### Horizontal
Use `scrollbar` with `overflow-x-auto` for horizontal scrolling.
<Preview>
<div class="card w-full max-w-sm p-0">
<div class="scrollbar overflow-x-auto">
<div class="flex w-max gap-4 p-4">
<figure class="shrink-0">
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Ornella Binni</figcaption>
</figure>
<figure class="shrink-0">
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Tom Byrom</figcaption>
</figure>
<figure class="shrink-0">
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Vladimir Malyavko</figcaption>
</figure>
</div>
</div>
</div>
</Preview>
### Thin
Use `scrollbar-sm` for compact scrollable regions like menus, listboxes, sidebars, and command results.
<Preview class="w-full max-w-64">
<div class="card w-full max-w-sm p-1">
<div class="px-2 py-1.5 text-xs font-medium text-muted-foreground">Recent files</div>
<div class="scrollbar-sm max-h-48 overflow-y-auto">
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>project-roadmap.md</span>
<span class="text-xs text-muted-foreground">2m</span>
</button>
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>release-notes.md</span>
<span class="text-xs text-muted-foreground">8m</span>
</button>
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>api-contract.json</span>
<span class="text-xs text-muted-foreground">16m</span>
</button>
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>pricing-table.html</span>
<span class="text-xs text-muted-foreground">1h</span>
</button>
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>onboarding-flow.md</span>
<span class="text-xs text-muted-foreground">2h</span>
</button>
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>settings-panel.ts</span>
<span class="text-xs text-muted-foreground">3h</span>
</button>
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
<span>invoice-template.html</span>
<span class="text-xs text-muted-foreground">5h</span>
</button>
</div>
</div>
</Preview>
### RTL
Set `dir="rtl"` on the scrollable element or one of its ancestors to use right-to-left layout.
<Preview class="w-full max-w-42">
<div class="card h-72 p-0">
<div dir="rtl" class="scrollbar h-full overflow-y-auto p-4">
<div class="space-y-4">
<h4 class="text-sm leading-none font-medium">العلامات</h4>
<div class="text-sm">
<div class="border-b py-2">v1.2.0-beta.50</div>
<div class="border-b py-2">v1.2.0-beta.49</div>
<div class="border-b py-2">v1.2.0-beta.48</div>
<div class="border-b py-2">v1.2.0-beta.47</div>
<div class="border-b py-2">v1.2.0-beta.46</div>
<div class="border-b py-2">v1.2.0-beta.45</div>
<div class="border-b py-2">v1.2.0-beta.44</div>
<div class="border-b py-2">v1.2.0-beta.43</div>
<div class="border-b py-2">v1.2.0-beta.42</div>
<div class="border-b py-2">v1.2.0-beta.41</div>
<div class="border-b py-2">v1.2.0-beta.40</div>
<div class="border-b py-2">v1.2.0-beta.39</div>
<div class="py-2">v1.2.0-beta.38</div>
</div>
</div>
</div>
</div>
</Preview>
+296
View File
@@ -0,0 +1,296 @@
# Select
<Preview>
<div id="select-demo" class="select" data-placeholder="Select a fruit">
<button type="button" class="w-[180px]" id="select-demo-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-demo-listbox">
<span class="truncate">Select a fruit</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-demo-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-demo-listbox" aria-orientation="vertical" aria-labelledby="select-demo-trigger">
<div role="group" aria-labelledby="select-demo-fruits">
<span role="heading" id="select-demo-fruits">Fruits</span>
<div role="option" data-value="apple">Apple</div>
<div role="option" data-value="banana">Banana</div>
<div role="option" data-value="blueberry">Blueberry</div>
<div role="option" data-value="grapes">Grapes</div>
<div role="option" data-value="pineapple">Pineapple</div>
</div>
</div>
</div>
<input type="hidden" name="select-demo-value" value="" />
</div>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#select" }}>
This component ships a `select()` macro for Jinja and Nunjucks.
</Callout>
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Select component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/select.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Select script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/select.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your select HTML">
```html
<div id="select-theme" class="select" data-placeholder="Theme">
<button type="button" class="w-[180px]" id="select-theme-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-theme-listbox">
<span class="truncate">Theme</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-theme-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-theme-listbox" aria-orientation="vertical" aria-labelledby="select-theme-trigger">
<div role="option" data-value="light">Light</div>
<div role="option" data-value="dark">Dark</div>
<div role="option" data-value="system">System</div>
</div>
</div>
<input type="hidden" name="theme" value="" />
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="select"&gt;</code></dt>
<dd>
Select root. Add <code>data-placeholder</code> to show placeholder text when no value is selected. Supports <code>data-close-on-select="true"</code> for multiple select and <code>data-format="object"</code> for serialized object values.
<dl>
<dt><code>&lt;button type="button"&gt;</code></dt>
<dd>Trigger button. Use <code>aria-haspopup="listbox"</code>, <code>aria-expanded</code>, and <code>aria-controls</code>.</dd>
<dt><code>&lt;div data-popover aria-hidden="true"&gt;</code></dt>
<dd>
Popover content. Supports <code>data-side</code> and <code>data-align</code> through the shared Popover rules.
<dl>
<dt><code>&lt;div role="listbox"&gt;</code></dt>
<dd>
Options container. Add <code>aria-multiselectable="true"</code> for multiple selection.
<dl>
<dt><code>&lt;div role="option" data-value="..." data-label="..."&gt;</code></dt>
<dd>Selectable option. <code>data-value</code> is submitted. <code>data-label</code> is optional and controls the selected display label and <code>data-format="object"</code> label; when omitted, Basecoat uses trimmed text content.</dd>
<dt><code>&lt;hr role="separator"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Separator between groups or options.</dd>
<dt><code>&lt;div role="group"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Option group. Use <code>aria-labelledby</code> to connect it to its heading.</dd>
<dt><code>&lt;span role="heading"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Group heading.</dd>
</dl>
</dd>
</dl>
</dd>
<dt><code>&lt;input type="hidden"&gt;</code></dt>
<dd>Submitted value. Empty value means no selection when the root has <code>data-placeholder</code>. Single select stores a string; multiple select stores a JSON array. With <code>data-format="object"</code>, values are serialized as <code>&#123; value, label &#125;</code> objects.</dd>
</dl>
</dd>
</dl>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `change` | Event | Dispatched on selection changes with `event.detail.value` and `event.detail.selected`. |
| `select.refresh()` | Method | Rescans options after children change inside the existing `role="listbox"` element. |
## Examples
### Groups
<Preview>
<div id="select-groups" class="select" data-placeholder="Select a fruit">
<button type="button" class="w-[180px]" id="select-groups-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-groups-listbox">
<span class="truncate">Select a fruit</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-groups-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-groups-listbox" aria-orientation="vertical" aria-labelledby="select-groups-trigger">
<div role="group" aria-labelledby="select-groups-fruits">
<span role="heading" id="select-groups-fruits">Fruits</span>
<div role="option" data-value="apple">Apple</div>
<div role="option" data-value="banana">Banana</div>
<div role="option" data-value="blueberry">Blueberry</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="select-groups-vegetables">
<span role="heading" id="select-groups-vegetables">Vegetables</span>
<div role="option" data-value="carrot">Carrot</div>
<div role="option" data-value="broccoli">Broccoli</div>
<div role="option" data-value="spinach">Spinach</div>
</div>
</div>
</div>
<input type="hidden" name="select-groups-value" value="" />
</div>
</Preview>
### Scrollable
A select with many items that scrolls.
<Preview>
<div id="select-scrollable" class="select" data-placeholder="Select a timezone">
<button type="button" class="w-[280px]" id="select-scrollable-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-scrollable-listbox">
<span class="truncate">Select a timezone</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-scrollable-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-scrollable-listbox" class="scrollbar-sm max-h-70 overflow-y-auto" aria-orientation="vertical" aria-labelledby="select-scrollable-trigger">
<div role="group" aria-labelledby="select-scrollable-north-america">
<span role="heading" id="select-scrollable-north-america">North America</span>
<div role="option" data-value="est">Eastern Standard Time (EST)</div>
<div role="option" data-value="cst">Central Standard Time (CST)</div>
<div role="option" data-value="mst">Mountain Standard Time (MST)</div>
<div role="option" data-value="pst">Pacific Standard Time (PST)</div>
<div role="option" data-value="akst">Alaska Standard Time (AKST)</div>
<div role="option" data-value="hst">Hawaii Standard Time (HST)</div>
</div>
<div role="group" aria-labelledby="select-scrollable-europe-africa">
<span role="heading" id="select-scrollable-europe-africa">Europe &amp; Africa</span>
<div role="option" data-value="gmt">Greenwich Mean Time (GMT)</div>
<div role="option" data-value="cet">Central European Time (CET)</div>
<div role="option" data-value="eet">Eastern European Time (EET)</div>
<div role="option" data-value="west">Western European Summer Time (WEST)</div>
<div role="option" data-value="cat">Central Africa Time (CAT)</div>
<div role="option" data-value="eat">East Africa Time (EAT)</div>
</div>
<div role="group" aria-labelledby="select-scrollable-asia">
<span role="heading" id="select-scrollable-asia">Asia</span>
<div role="option" data-value="msk">Moscow Time (MSK)</div>
<div role="option" data-value="ist">India Standard Time (IST)</div>
<div role="option" data-value="cst_china">China Standard Time (CST)</div>
<div role="option" data-value="jst">Japan Standard Time (JST)</div>
<div role="option" data-value="kst">Korea Standard Time (KST)</div>
<div role="option" data-value="ist_indonesia">Indonesia Central Standard Time (WITA)</div>
</div>
<div role="group" aria-labelledby="select-scrollable-australia-pacific">
<span role="heading" id="select-scrollable-australia-pacific">Australia &amp; Pacific</span>
<div role="option" data-value="awst">Australian Western Standard Time (AWST)</div>
<div role="option" data-value="acst">Australian Central Standard Time (ACST)</div>
<div role="option" data-value="aest">Australian Eastern Standard Time (AEST)</div>
<div role="option" data-value="nzst">New Zealand Standard Time (NZST)</div>
<div role="option" data-value="fjt">Fiji Time (FJT)</div>
</div>
<div role="group" aria-labelledby="select-scrollable-south-america">
<span role="heading" id="select-scrollable-south-america">South America</span>
<div role="option" data-value="art">Argentina Time (ART)</div>
<div role="option" data-value="bot">Bolivia Time (BOT)</div>
<div role="option" data-value="brt">Brasilia Time (BRT)</div>
<div role="option" data-value="clt">Chile Standard Time (CLT)</div>
</div>
</div>
</div>
<input type="hidden" name="select-scrollable-value" value="" />
</div>
</Preview>
### Disabled
<Preview>
<div id="select-disabled" class="select" data-placeholder="Select a fruit">
<button type="button" class="w-[180px]" id="select-disabled-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-disabled-listbox" disabled>
<span class="truncate">Select a fruit</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-disabled-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-disabled-listbox" aria-orientation="vertical" aria-labelledby="select-disabled-trigger">
<div role="option" data-value="apple">Apple</div>
<div role="option" data-value="banana">Banana</div>
<div role="option" data-value="blueberry">Blueberry</div>
<div role="option" data-value="grapes" aria-disabled="true">Grapes</div>
<div role="option" data-value="pineapple">Pineapple</div>
</div>
</div>
<input type="hidden" name="select-disabled-value" value="" />
</div>
</Preview>
### Invalid
<Preview>
<div class="field" data-invalid="true">
<label for="select-invalid-trigger">Fruit</label>
<div id="select-invalid" class="select" data-placeholder="Select a fruit">
<button type="button" class="w-[180px]" id="select-invalid-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-invalid-listbox" aria-invalid="true">
<span class="truncate">Select a fruit</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-invalid-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-invalid-listbox" aria-orientation="vertical" aria-labelledby="select-invalid-trigger">
<div role="option" data-value="apple">Apple</div>
<div role="option" data-value="banana">Banana</div>
<div role="option" data-value="blueberry">Blueberry</div>
<div role="option" data-value="grapes">Grapes</div>
<div role="option" data-value="pineapple">Pineapple</div>
</div>
</div>
<input type="hidden" name="fruit" value="" />
</div>
<p role="alert">Please select a valid fruit.</p>
</div>
</Preview>
### RTL
<Preview>
<div dir="rtl">
<div id="select-rtl" class="select" data-placeholder="اختر فاكهة">
<button type="button" class="w-[180px]" id="select-rtl-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-rtl-listbox">
<span class="truncate">اختر فاكهة</span>
<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="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
</button>
<div id="select-rtl-popover" data-popover aria-hidden="true">
<div role="listbox" id="select-rtl-listbox" aria-orientation="vertical" aria-labelledby="select-rtl-trigger">
<div role="group" aria-labelledby="select-rtl-fruits">
<span role="heading" id="select-rtl-fruits">الفواكه</span>
<div role="option" data-value="apple">تفاح</div>
<div role="option" data-value="banana">موز</div>
<div role="option" data-value="blueberry">توت أزرق</div>
<div role="option" data-value="grapes">عنب</div>
<div role="option" data-value="pineapple">أناناس</div>
</div>
</div>
</div>
<input type="hidden" name="select-rtl-value" value="" />
</div>
</div>
</Preview>
+158
View File
@@ -0,0 +1,158 @@
# Sidebar
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#sidebar" }}>
This component ships a `sidebar()` macro for Jinja and Nunjucks.
</Callout>
Sidebar uses a native `<aside>` next to your page content. Basecoat owns the fixed positioning, overlay behavior, active states, nested lists, and `details` submenu styling. Your app still owns the navigation data and current route.
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Sidebar component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/sidebar.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Sidebar script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/sidebar.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your sidebar HTML">
```html
<aside id="sidebar" class="sidebar" data-side="left">
<nav aria-label="Sidebar navigation">
<section class="scrollbar-sm">
<div role="group" aria-labelledby="group-label-content-1">
<h3 id="group-label-content-1">Getting started</h3>
<ul>
<li>
<a href="#">
<svg class="lucide lucide-square-terminal" 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"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></svg>
<span>Playground</span>
</a>
</li>
<li>
<a href="#">
<svg class="lucide lucide-bot" 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"><path d="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" /></svg>
<span>Models</span>
</a>
</li>
<li>
<details id="submenu-content-1-3">
<summary aria-controls="submenu-content-1-3-content"><svg class="lucide lucide-settings" 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"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
<span>Settings</span>
</summary>
<ul id="submenu-content-1-3-content">
<li><a href="#"><span>General</span></a></li>
<li><a href="#"><span>Team</span></a></li>
<li><a href="#"><span>Billing</span></a></li>
<li><a href="#"><span>Limits</span></a></li>
</ul>
</details>
</li>
</ul>
</div>
</section>
</nav>
</aside>
<main>
<button type="button" onclick="document.getElementById('sidebar')?.toggle()">Toggle sidebar</button>
<h1>Content</h1>
</main>
```
</Step>
</Steps>
### RTL
Set `dir="rtl"` on the sidebar or a parent element. Logical spacing, nested borders, and item content follow document direction. `data-side` is physical: `left` and `right` refer to viewport sides.
<Callout type="warning" title="Differences with shadcn/ui" icon="triangle-alert">
Basecoat intentionally supports the stable HTML sidebar surface: fixed left/right sidebars, mobile overlay behavior, grouped navigation, active states, and native `details` submenus. It does not expose shadcn/ui's React provider, rail, inset/floating variants, icon-only collapse mode, menu actions, menu badges, or menu skeleton API.
</Callout>
### HTML structure
Basecoat maps shadcn/ui's sidebar composition to regular navigation HTML: an `<aside>` root, one `<nav>`, optional header/footer regions, grouped lists, item controls, and native `<details>` for nested menus.
<dl>
<dt><code>&lt;aside class="sidebar"&gt;</code></dt>
<dd>
Sidebar root. Supports <code>id</code>, <code>data-side="left|right"</code>, <code>data-initial-open="false"</code>, <code>data-initial-mobile-open="true"</code>, and <code>data-breakpoint</code>. JavaScript manages <code>aria-hidden</code>, <code>inert</code>, and the internal <code>data-sidebar-initialized</code> flag.
<dl>
<dt><code>&lt;nav aria-label="..."&gt;</code></dt>
<dd>
Navigation landmark inside the sidebar.
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>Optional header for branding, workspace controls, user controls, or persistent actions.</dd>
<dt><code>&lt;section&gt;</code></dt>
<dd>
Scrollable content region between the optional header and footer.
<dl>
<dt><code>&lt;div role="group" aria-labelledby="..."&gt;</code></dt>
<dd>Named navigation group. Use a heading element as the group label.</dd>
<dt><code>&lt;ul&gt;</code> + <code>&lt;li&gt;</code></dt>
<dd>
Menu and menu item structure. Top-level lists and nested submenu lists receive different spacing and borders.
<dl>
<dt><code>&lt;a&gt;</code> / <code>&lt;button&gt;</code></dt>
<dd>Menu item control. Use links for navigation and buttons for actions. Supports <code>data-variant="default|outline"</code>, <code>data-size="default|sm|lg"</code>, <code>data-active="true"</code>, <code>disabled</code> on buttons, and <code>aria-disabled="true"</code> on custom-disabled controls. For links, prefer <code>&lt;a aria-current="page"&gt;</code> for the current page. Add <code>data-keep-mobile-sidebar-open</code> on a clicked link, button, or ancestor when that control should not close the mobile sidebar.</dd>
<dt><code>&lt;details&gt;</code> / <code>&lt;summary&gt;</code></dt>
<dd>Native disclosure for collapsible submenus. Put the nested <code>&lt;ul&gt;</code> directly after <code>&lt;summary&gt;</code>. The <code>&lt;summary&gt;</code> supports the same active, variant, and size attributes as menu item controls. Add <code>id</code> and <code>aria-controls</code> when the relationship is not obvious from nearby markup.</dd>
<dt><code>&lt;hr role="separator"&gt;</code></dt>
<dd>Optional separator between groups or menu sections.</dd>
</dl>
</dd>
</dl>
</dd>
<dt><code>&lt;footer&gt;</code></dt>
<dd>Optional footer for account controls, status, or persistent actions.</dd>
</dl>
</dd>
</dl>
</dd>
<dt><code>&lt;main&gt;</code></dt>
<dd>Sibling content wrapper. The desktop margin applies to the sibling immediately after the sidebar.</dd>
</dl>
Set `--sidebar-width` and `--sidebar-mobile-width` on `:root` or the sidebar root to override the default `16rem` desktop width and `18rem` mobile width.
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `sidebar.open()` | Method | Opens the sidebar. |
| `sidebar.close()` | Method | Closes the sidebar. |
| `sidebar.toggle()` | Method | Toggles the sidebar. |
+134
View File
@@ -0,0 +1,134 @@
# Skeleton
<Preview>
<div class="flex items-center gap-4">
<div class="skeleton size-10 shrink-0 rounded-full"></div>
<div class="grid gap-2">
<div class="skeleton h-4 w-[250px]"></div>
<div class="skeleton h-4 w-[200px]"></div>
</div>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Skeleton component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/skeleton.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your skeleton HTML">
Add `class="skeleton"` with sizing utilities to create loading placeholders.
```html
<div class="skeleton h-4 w-[150px]"></div>
```
</Step>
</Steps>
## Examples
### Avatar
<Preview>
<div class="flex items-center gap-4">
<div class="skeleton size-10 shrink-0 rounded-full"></div>
<div class="grid gap-2">
<div class="skeleton h-4 w-[150px]"></div>
<div class="skeleton h-4 w-[100px]"></div>
</div>
</div>
</Preview>
### Card
<Preview class="w-full max-w-xs">
<div class="card w-full">
<header>
<div class="skeleton h-4 w-2/3"></div>
<div class="skeleton h-4 w-1/2"></div>
</header>
<section> <div class="skeleton aspect-video w-full"></div></section>
</div>
</Preview>
### Text
<Preview class="w-full max-w-sm">
<div class="flex flex-col gap-2">
<div class="skeleton h-4 w-full"></div>
<div class="skeleton h-4 w-full"></div>
<div class="skeleton h-4 w-3/4"></div>
</div>
</Preview>
### Form
<Preview class="w-full max-w-xs">
<div class="flex flex-col gap-7">
<div class="flex flex-col gap-3">
<div class="skeleton h-4 w-20"></div>
<div class="skeleton h-10 w-full"></div>
</div>
<div class="flex flex-col gap-3">
<div class="skeleton h-4 w-24"></div>
<div class="skeleton h-10 w-full"></div>
</div>
<div class="skeleton h-9 w-24"></div>
</div>
</Preview>
### Table
<Preview class="w-full max-w-sm">
<div class="flex flex-col gap-2">
<div class="flex gap-4">
<div class="skeleton h-4 flex-1"></div>
<div class="skeleton h-4 w-24"></div>
<div class="skeleton h-4 w-20"></div>
</div>
<div class="flex gap-4">
<div class="skeleton h-4 flex-1"></div>
<div class="skeleton h-4 w-24"></div>
<div class="skeleton h-4 w-20"></div>
</div>
<div class="flex gap-4">
<div class="skeleton h-4 flex-1"></div>
<div class="skeleton h-4 w-24"></div>
<div class="skeleton h-4 w-20"></div>
</div>
</div>
</Preview>
### RTL
Skeleton blocks are direction-neutral. Use logical layout utilities around them when spacing depends on direction.
<Preview>
<div dir="rtl" class="flex items-center gap-4">
<div class="skeleton size-10 shrink-0 rounded-full"></div>
<div class="grid gap-2">
<div class="skeleton h-4 w-[150px]"></div>
<div class="skeleton h-4 w-[100px]"></div>
</div>
</div>
</Preview>
+115
View File
@@ -0,0 +1,115 @@
# Slider
<Preview class="w-full max-w-sm">
<input
type="range"
class="input w-full"
min="0"
max="100"
value="50"
/>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Slider component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/range.css";
@import "basecoat-css/styles/vega.css";
```
Slider uses the Range component CSS.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Slider script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/range.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your slider HTML">
Use a native `<input type="range" class="input">`. Include `range.js` so Basecoat can keep the filled track in sync with the input value.
```html
<input type="range" class="input w-full" min="0" max="100" value="50" />
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;input type="range" class="input"&gt;</code></dt>
<dd>Native range input styled as a slider. Width utilities such as <code>w-full</code> can be added directly.</dd>
</dl>
Basecoat intentionally uses the native range input. Unlike shadcn/ui's Base UI slider, it does not support multiple thumbs or vertical orientation.
## Examples
### Default
<Preview class="w-full max-w-sm">
<input type="range" class="input w-full" min="0" max="100" value="50" />
</Preview>
### Label
<Preview class="w-full max-w-sm">
<div class="grid w-full gap-1 max-w-sm">
<div class="flex items-center justify-between gap-2">
<label class="label" for="temperature">Temperature</label>
<output class="text-sm text-muted-foreground" for="temperature">50</output>
</div>
<input
id="temperature"
type="range"
class="input w-full"
min="0"
max="100"
value="50"
oninput="this.previousElementSibling.querySelector('output').value = this.value"
/>
</div>
</Preview>
### Disabled
<Preview class="w-full max-w-sm">
<input type="range" class="input w-full" min="0" max="100" value="50" disabled />
</Preview>
### RTL
<Preview class="w-full max-w-sm">
<input type="range" class="input w-full" min="0" max="100" value="50" dir="rtl" />
</Preview>
+138
View File
@@ -0,0 +1,138 @@
# Spinner
<Callout title="No dedicated component" icon="circle-alert">
There is no dedicated Spinner component in Basecoat.
</Callout>
<Preview class="w-full max-w-xs">
<article class="item bg-muted/50 border-transparent">
<figure><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></figure>
<section> <h3>Processing payment...</h3></section>
<aside class="tabular-nums text-sm">$100.00</aside>
</article>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import Tailwind only when the component is not composed with other Basecoat components.
```css
@import "tailwindcss";
```
Spinner is pure HTML plus Tailwind utilities and has no dedicated component CSS file.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your spinner HTML">
Spinners are pure HTML using the `loader-circle` Lucide icon with the `animate-spin` Tailwind utility. Add `role="status"` and `aria-label="Loading"` for accessibility.
```html
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
```
</Step>
</Steps>
## Examples
### Size
Use the `size-*` utility class to change the size of the spinner.
<Preview>
<div class="flex items-center gap-6"><svg aria-label="Loading" role="status" class="size-3 animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg><svg aria-label="Loading" role="status" class="size-4 animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg><svg aria-label="Loading" role="status" class="size-6 animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg><svg aria-label="Loading" role="status" class="size-8 animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></div>
</Preview>
### Button
Add a spinner to a button to indicate a loading state. Place the spinner before or after the label; button spacing is handled automatically.
<Preview>
<div class="flex flex-col items-center gap-4">
<button class="btn" data-size="sm" disabled><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Loading...
</button>
<button class="btn" data-variant="outline" data-size="sm" disabled><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Please wait
</button>
<button class="btn" data-variant="secondary" data-size="sm" disabled><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Processing
</button>
</div>
</Preview>
### Badge
Add a spinner to a badge to indicate a loading state. Place the spinner before or after the label; badge spacing is handled automatically.
<Preview>
<div class="flex items-center gap-4">
<span class="badge"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Syncing
</span>
<span class="badge" data-variant="secondary"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Updating
</span>
<span class="badge" data-variant="outline"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Processing
</span>
</div>
</Preview>
### Input group
<Preview class="w-full max-w-md">
<div class="flex w-full max-w-md flex-col gap-4">
<div class="input-group">
<input placeholder="Send a message..." disabled />
<span data-align="inline-end"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></span>
</div>
<div class="input-group">
<textarea placeholder="Send a message..." disabled></textarea>
<footer data-align="block-end"><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
Validating...
<button type="button" class="btn ms-auto" data-size="icon-sm"><svg class="lucide lucide-arrow-up" 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"><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
<span class="sr-only">Send</span>
</button>
</footer>
</div>
</div>
</Preview>
### Empty
<Preview class="w-full">
<div class="empty w-full">
<header>
<figure><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></figure>
<h3>Processing your request</h3>
<p>Please wait while we process your request. Do not refresh the page.</p>
</header>
<section> <button class="btn" data-variant="outline" data-size="sm">Cancel</button></section>
</div>
</Preview>
## RTL
Spinner layout follows document direction. Use logical utilities when surrounding spacing depends on direction.
<Preview class="w-full max-w-xs">
<article class="item bg-muted/50 border-transparent" dir="rtl">
<figure><svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" 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"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></figure>
<section> <h3>جاري معالجة الدفع...</h3></section>
<aside class="tabular-nums text-sm">١٠٠.٠٠ دولار</aside>
</article>
</Preview>
+149
View File
@@ -0,0 +1,149 @@
# Switch
<Preview>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="airplane-mode" role="switch" class="input" />
<label for="airplane-mode">Airplane Mode</label>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Switch component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/switch.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your switch HTML">
Add `class="input"` and `role="switch"` to a native checkbox. Use a native `<label>` or `aria-label` for accessible naming.
```html
<input type="checkbox" role="switch" class="input" />
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;input type="checkbox" role="switch" class="input"&gt;</code></dt>
<dd>The switch control. Use native checkbox state: add <code>checked</code> for an initially enabled switch, <code>disabled</code> for a disabled switch, and <code>aria-invalid="true"</code> for invalid state.</dd>
<dt><code>data-size="sm"</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Renders the smaller switch size.</dd>
</dl>
## Examples
### Description
<Preview class="w-full max-w-sm">
<div role="group" class="field" data-orientation="horizontal">
<section>
<label for="switch-focus-mode">Share across devices</label>
<p id="switch-focus-mode-description">Focus is shared across devices, and turns off when you leave the app.</p>
</section>
<input type="checkbox" id="switch-focus-mode" role="switch" class="input" aria-describedby="switch-focus-mode-description" />
</div>
</Preview>
### Choice card
Use a wrapping `<label>` when the whole card should toggle the switch.
<Preview class="w-full max-w-sm">
<div role="group" class="grid w-full gap-3" aria-label="Focus settings">
<label>
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Share across devices</h3>
<p>Focus is shared across devices, and turns off when you leave the app.</p>
</section>
<input type="checkbox" role="switch" class="input" />
</div>
</label>
<label>
<div role="group" class="field" data-orientation="horizontal">
<section>
<h3>Enable notifications</h3>
<p>Receive notifications when focus mode changes.</p>
</section>
<input type="checkbox" role="switch" class="input" checked />
</div>
</label>
</div>
</Preview>
### Disabled
Add `disabled` to the switch and `data-disabled="true"` to the field wrapper when the surrounding field should use disabled styling.
<Preview>
<div role="group" class="field" data-orientation="horizontal" data-disabled="true">
<input type="checkbox" id="switch-disabled-unchecked" role="switch" class="input" disabled />
<label for="switch-disabled-unchecked">Disabled</label>
</div>
</Preview>
### Invalid
Add `aria-invalid="true"` to the switch and `data-invalid="true"` to the field wrapper.
<Preview class="w-full max-w-sm">
<div role="group" class="field" data-orientation="horizontal" data-invalid="true">
<section>
<label for="switch-invalid">Accept terms and conditions.</label>
<p>You must accept the terms and conditions to continue.</p>
</section>
<input type="checkbox" id="switch-invalid" role="switch" class="input" aria-invalid="true" />
</div>
</Preview>
### Size
Use `data-size="sm"` to render the smaller switch size.
<Preview class="w-full max-w-[10rem]">
<div role="group" class="grid w-full gap-4" aria-label="Switch sizes">
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="switch-size-sm" role="switch" class="input" data-size="sm" />
<label for="switch-size-sm">Small</label>
</div>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="switch-size-default" role="switch" class="input" />
<label for="switch-size-default">Default</label>
</div>
</div>
</Preview>
## RTL
Switch field layout uses document direction. Set `dir="rtl"` on the field or an ancestor.
<Preview class="w-full max-w-sm">
<div dir="rtl" role="group" class="field" data-orientation="horizontal">
<section>
<label for="switch-rtl">المشاركة عبر الأجهزة</label>
<p id="switch-rtl-description">يتم مشاركة التركيز عبر الأجهزة، ويتم إيقاف تشغيله عند مغادرة التطبيق.</p>
</section>
<input type="checkbox" id="switch-rtl" role="switch" class="input" aria-describedby="switch-rtl-description" checked />
</div>
</Preview>
+232
View File
@@ -0,0 +1,232 @@
# Table
<Preview class="w-full">
<div class="table-container">
<table class="table">
<caption>A list of your recent invoices.</caption>
<thead>
<tr>
<th>Invoice</th>
<th>Status</th>
<th>Method</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-medium">INV001</td>
<td>Paid</td>
<td>Credit Card</td>
<td class="text-end">$250.00</td>
</tr>
<tr>
<td class="font-medium">INV002</td>
<td>Pending</td>
<td>PayPal</td>
<td class="text-end">$150.00</td>
</tr>
<tr>
<td class="font-medium">INV003</td>
<td>Unpaid</td>
<td>Bank Transfer</td>
<td class="text-end">$350.00</td>
</tr>
<tr>
<td class="font-medium">INV004</td>
<td>Paid</td>
<td>Paypal</td>
<td class="text-end">$450.00</td>
</tr>
<tr>
<td class="font-medium">INV005</td>
<td>Paid</td>
<td>Credit Card</td>
<td class="text-end">$550.00</td>
</tr>
<tr>
<td class="font-medium">INV006</td>
<td>Pending</td>
<td>Bank Transfer</td>
<td class="text-end">$200.00</td>
</tr>
<tr>
<td class="font-medium">INV007</td>
<td>Unpaid</td>
<td>Credit Card</td>
<td class="text-end">$300.00</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">Total</td>
<td class="text-end">$2,500.00</td>
</tr>
</tfoot>
</table>
</div>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Table component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/table.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your table HTML">
Add `class="table"` to your table element. Wrap it in `class="table-container"` when horizontal scrolling is needed.
```html
<div class="table-container">
<table class="table">
<!-- Content of your table -->
</table>
</div>
```
</Step>
</Steps>
<Callout type="warning" title="Scrollable tables clip overlays" icon="triangle-alert">
Do not place inline popovers, dropdown menus, selects, or tooltips inside `.table-container`. The table container uses horizontal overflow for wide tables, so absolutely positioned overlays can be clipped or create scrollbars. Keep overlay actions outside scrollable table regions, or omit `.table-container` when the table does not need horizontal scrolling.
</Callout>
## Examples
Examples below follow upstream table patterns where they map cleanly to Basecoat's non-portalled HTML model.
### With Footer
<Preview class="w-full">
<div class="table-container">
<table class="table">
<caption>A list of your recent invoices.</caption>
<thead>
<tr>
<th class="w-[100px]">Invoice</th>
<th>Status</th>
<th>Method</th>
<th class="text-end">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-medium">INV001</td>
<td>Paid</td>
<td>Credit Card</td>
<td class="text-end">$250.00</td>
</tr>
<tr>
<td class="font-medium">INV002</td>
<td>Pending</td>
<td>PayPal</td>
<td class="text-end">$150.00</td>
</tr>
<tr>
<td class="font-medium">INV003</td>
<td>Unpaid</td>
<td>Bank Transfer</td>
<td class="text-end">$350.00</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">Total</td>
<td class="text-end">$2,500.00</td>
</tr>
</tfoot>
</table>
</div>
</Preview>
### With Actions
<Preview class="w-full">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-medium">Wireless Mouse</td>
<td>$29.99</td>
<td class="text-end">
<div class="inline-flex gap-2">
<button type="button" class="btn" data-variant="outline" data-size="sm">Edit</button>
<button type="button" class="btn" data-variant="destructive" data-size="sm">Delete</button>
</div>
</td>
</tr>
<tr>
<td class="font-medium">Mechanical Keyboard</td>
<td>$129.99</td>
<td class="text-end">
<div class="inline-flex gap-2">
<button type="button" class="btn" data-variant="outline" data-size="sm">Edit</button>
<button type="button" class="btn" data-variant="destructive" data-size="sm">Delete</button>
</div>
</td>
</tr>
<tr>
<td class="font-medium">USB-C Hub</td>
<td>$49.99</td>
<td class="text-end">
<div class="inline-flex gap-2">
<button type="button" class="btn" data-variant="outline" data-size="sm">Edit</button>
<button type="button" class="btn" data-variant="destructive" data-size="sm">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</Preview>
### RTL
Set `dir="rtl"` on the table container when needed. Use logical alignment utilities such as `text-end`.
<Preview class="w-full">
<div class="table-container" dir="rtl">
<table class="table">
<caption>قائمة الفواتير الأخيرة.</caption>
<thead>
<tr>
<th>الفاتورة</th>
<th>الحالة</th>
<th class="text-end">المبلغ</th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-medium">INV001</td>
<td>مدفوعة</td>
<td class="text-end">$250.00</td>
</tr>
</tbody>
</table>
</div>
</Preview>
+307
View File
@@ -0,0 +1,307 @@
# Tabs
<Preview class="max-w-[400px] w-full">
<div class="tabs w-full" id="demo-tabs-with-panels">
<nav role="tablist" aria-orientation="horizontal" class="w-full">
<button type="button" role="tab" id="demo-tabs-with-panels-tab-1" aria-controls="demo-tabs-with-panels-panel-1" aria-selected="true" tabindex="0">Account</button>
<button type="button" role="tab" id="demo-tabs-with-panels-tab-2" aria-controls="demo-tabs-with-panels-panel-2" aria-selected="false" tabindex="-1">Password</button>
</nav>
<div role="tabpanel" id="demo-tabs-with-panels-panel-1" aria-labelledby="demo-tabs-with-panels-tab-1" tabindex="-1" aria-selected="true">
<div class="card">
<header>
<h2>Account</h2>
<p>Make changes to your account here. Click save when you're done.</p>
</header>
<section>
<form class="grid gap-4">
<div role="group" class="field">
<label for="demo-tabs-account-name">Name</label>
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
</div>
<div role="group" class="field">
<label for="demo-tabs-account-username">Username</label>
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
</div>
</form>
</section>
<footer>
<button type="button" class="btn">Save changes</button>
</footer>
</div>
</div>
<div role="tabpanel" id="demo-tabs-with-panels-panel-2" aria-labelledby="demo-tabs-with-panels-tab-2" tabindex="-1" aria-selected="false" hidden>
<div class="card">
<header>
<h2>Password</h2>
<p>Change your password here. After saving, you'll be logged out.</p>
</header>
<section>
<form class="grid gap-4">
<div role="group" class="field">
<label for="demo-tabs-password-current">Current password</label>
<input type="password" id="demo-tabs-password-current" />
</div>
<div role="group" class="field">
<label for="demo-tabs-password-new">New password</label>
<input type="password" id="demo-tabs-password-new" />
</div>
</form>
</section>
<footer>
<button type="button" class="btn">Save password</button>
</footer>
</div>
</div>
</div>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#tabs" }}>
This component ships a `tabs()` macro for Jinja and Nunjucks.
</Callout>
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Tabs component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/tabs.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Tabs script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/tabs.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your tabs HTML">
```html
<div class="tabs w-full" id="demo-tabs-with-panels">
<nav role="tablist" aria-orientation="horizontal" class="w-full">
<button type="button" role="tab" id="demo-tabs-with-panels-tab-1" aria-controls="demo-tabs-with-panels-panel-1" aria-selected="true" tabindex="0">Account</button>
<button type="button" role="tab" id="demo-tabs-with-panels-tab-2" aria-controls="demo-tabs-with-panels-panel-2" aria-selected="false" tabindex="-1">Password</button>
</nav>
<div role="tabpanel" id="demo-tabs-with-panels-panel-1" aria-labelledby="demo-tabs-with-panels-tab-1" tabindex="-1" aria-selected="true">
<div class="card">
<header>
<h2>Account</h2>
<p>Make changes to your account here. Click save when you're done.</p>
</header>
<section>
<form class="grid gap-4">
<div role="group" class="field">
<label for="demo-tabs-account-name">Name</label>
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
</div>
<div role="group" class="field">
<label for="demo-tabs-account-username">Username</label>
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
</div>
</form>
</section>
<footer>
<button type="button" class="btn">Save changes</button>
</footer>
</div>
</div>
<div role="tabpanel" id="demo-tabs-with-panels-panel-2" aria-labelledby="demo-tabs-with-panels-tab-2" tabindex="-1" aria-selected="false" hidden>
<div class="card">
<header>
<h2>Password</h2>
<p>Change your password here. After saving, you'll be logged out.</p>
</header>
<section>
<form class="grid gap-4">
<div role="group" class="field">
<label for="demo-tabs-password-current">Current password</label>
<input type="password" id="demo-tabs-password-current" />
</div>
<div role="group" class="field">
<label for="demo-tabs-password-new">New password</label>
<input type="password" id="demo-tabs-password-new" />
</div>
</form>
</section>
<footer>
<button type="button" class="btn">Save password</button>
</footer>
</div>
</div>
</div>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div class="tabs"&gt;</code></dt>
<dd>
Root tabs container. It becomes horizontal or vertical based on the child tablist's <code>aria-orientation</code>.
<dl>
<dt><code>&lt;nav role="tablist" aria-orientation="horizontal"&gt;</code></dt>
<dd>
Tablist containing tab buttons. Set <code>aria-orientation="vertical"</code> for vertical tabs and <code>data-variant="line"</code> for the line style.
<dl>
<dt><code>&lt;button role="tab" id="&#123; TAB_ID &#125;" aria-controls="&#123; PANEL_ID &#125;" aria-selected="true" tabindex="0"&gt;</code></dt>
<dd>Tab trigger. The active tab has <code>aria-selected="true"</code> and <code>tabindex="0"</code>. Inactive tabs use <code>tabindex="-1"</code>. Disabled tabs use <code>disabled</code> or <code>aria-disabled="true"</code>.</dd>
</dl>
</dd>
<dt><code>&lt;div role="tabpanel" id="&#123; PANEL_ID &#125;" aria-labelledby="&#123; TAB_ID &#125;"&gt;</code></dt>
<dd>Panel controlled by a tab. Inactive panels are hidden.</dd>
</dl>
</dd>
</dl>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `tabs.select(tab)` | Method | Selects a tab button in the tablist. |
| `tabs.refresh()` | Method | Rescans tabs and panels after children change inside the existing tablist/panel structure. |
## Examples
### Line
<Preview>
<div class="tabs" id="tabs-line">
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
<button type="button" role="tab" id="tabs-line-tab-overview" aria-controls="tabs-line-panel-overview" aria-selected="true" tabindex="0">Overview</button>
<button type="button" role="tab" id="tabs-line-tab-analytics" aria-controls="tabs-line-panel-analytics" aria-selected="false" tabindex="-1">Analytics</button>
<button type="button" role="tab" id="tabs-line-tab-reports" aria-controls="tabs-line-panel-reports" aria-selected="false" tabindex="-1">Reports</button>
</nav>
<div role="tabpanel" id="tabs-line-panel-overview" aria-labelledby="tabs-line-tab-overview" tabindex="-1">Overview content.</div>
<div role="tabpanel" id="tabs-line-panel-analytics" aria-labelledby="tabs-line-tab-analytics" tabindex="-1" hidden>Analytics content.</div>
<div role="tabpanel" id="tabs-line-panel-reports" aria-labelledby="tabs-line-tab-reports" tabindex="-1" hidden>Reports content.</div>
</div>
</Preview>
### Vertical
<Preview>
<div class="tabs" id="tabs-vertical">
<nav role="tablist" aria-orientation="vertical">
<button type="button" role="tab" id="tabs-vertical-tab-account" aria-controls="tabs-vertical-panel-account" aria-selected="true" tabindex="0">Account</button>
<button type="button" role="tab" id="tabs-vertical-tab-password" aria-controls="tabs-vertical-panel-password" aria-selected="false" tabindex="-1">Password</button>
<button type="button" role="tab" id="tabs-vertical-tab-notifications" aria-controls="tabs-vertical-panel-notifications" aria-selected="false" tabindex="-1">Notifications</button>
</nav>
<div role="tabpanel" id="tabs-vertical-panel-account" aria-labelledby="tabs-vertical-tab-account" tabindex="-1">Account content.</div>
<div role="tabpanel" id="tabs-vertical-panel-password" aria-labelledby="tabs-vertical-tab-password" tabindex="-1" hidden>Password content.</div>
<div role="tabpanel" id="tabs-vertical-panel-notifications" aria-labelledby="tabs-vertical-tab-notifications" tabindex="-1" hidden>Notifications content.</div>
</div>
</Preview>
### Disabled
<Preview>
<div class="tabs" id="tabs-disabled">
<nav role="tablist" aria-orientation="horizontal">
<button type="button" role="tab" id="tabs-disabled-tab-home" aria-controls="tabs-disabled-panel-home" aria-selected="true" tabindex="0">Home</button>
<button type="button" role="tab" id="tabs-disabled-tab-settings" aria-controls="tabs-disabled-panel-settings" aria-selected="false" tabindex="-1" disabled>Disabled</button>
</nav>
<div role="tabpanel" id="tabs-disabled-panel-home" aria-labelledby="tabs-disabled-tab-home" tabindex="-1">Home content.</div>
<div role="tabpanel" id="tabs-disabled-panel-settings" aria-labelledby="tabs-disabled-tab-settings" tabindex="-1" hidden>Settings content.</div>
</div>
</Preview>
### Icons
<Preview>
<div class="tabs" id="tabs-with-icons">
<nav role="tablist" aria-orientation="horizontal">
<button type="button" role="tab" id="tabs-with-icons-tab-preview" aria-controls="tabs-with-icons-panel-preview" aria-selected="true" tabindex="0"><svg class="lucide lucide-app-window" 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"><rect x="2" y="4" width="20" height="16" rx="2" /><path d="M10 4v4" /><path d="M2 8h20" /><path d="M6 4v4" /></svg> Preview</button>
<button type="button" role="tab" id="tabs-with-icons-tab-code" aria-controls="tabs-with-icons-panel-code" aria-selected="false" tabindex="-1"><svg class="lucide lucide-code" 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"><path d="m16 18 6-6-6-6" /><path d="m8 6-6 6 6 6" /></svg> Code</button>
</nav>
<div role="tabpanel" id="tabs-with-icons-panel-preview" aria-labelledby="tabs-with-icons-tab-preview" tabindex="-1">Preview content.</div>
<div role="tabpanel" id="tabs-with-icons-panel-code" aria-labelledby="tabs-with-icons-tab-code" tabindex="-1" hidden>Code content.</div>
</div>
</Preview>
## RTL
To enable RTL support, set `dir="rtl"` on the tabs root or a parent element.
<Preview>
<div class="tabs w-full max-w-sm" id="tabs-rtl" dir="rtl">
<nav role="tablist" aria-orientation="horizontal" dir="rtl">
<button type="button" role="tab" id="tabs-rtl-tab-overview" aria-controls="tabs-rtl-panel-overview" aria-selected="true" tabindex="0">نظرة عامة</button>
<button type="button" role="tab" id="tabs-rtl-tab-analytics" aria-controls="tabs-rtl-panel-analytics" aria-selected="false" tabindex="-1">التحليلات</button>
<button type="button" role="tab" id="tabs-rtl-tab-reports" aria-controls="tabs-rtl-panel-reports" aria-selected="false" tabindex="-1">التقارير</button>
<button type="button" role="tab" id="tabs-rtl-tab-settings" aria-controls="tabs-rtl-panel-settings" aria-selected="false" tabindex="-1">الإعدادات</button>
</nav>
<div role="tabpanel" id="tabs-rtl-panel-overview" aria-labelledby="tabs-rtl-tab-overview" tabindex="-1">
<div class="card">
<header>
<h2>نظرة عامة</h2>
<p>عرض مقاييسك الرئيسية وأنشطة المشروع الأخيرة. تتبع التقدم عبر جميع مشاريعك النشطة.</p>
</header>
<section>
<p class="text-muted-foreground text-sm">لديك ١٢ مشروعًا نشطًا و٣ مهام معلقة.</p>
</section>
</div>
</div>
<div role="tabpanel" id="tabs-rtl-panel-analytics" aria-labelledby="tabs-rtl-tab-analytics" tabindex="-1" hidden>
<div class="card">
<header>
<h2>التحليلات</h2>
<p>تتبع مقاييس الأداء ومشاركة المستخدمين. راقب الاتجاهات وحدد فرص النمو.</p>
</header>
<section>
<p class="text-muted-foreground text-sm">زادت مشاهدات الصفحة بنسبة ٢٥٪ مقارنة بالشهر الماضي.</p>
</section>
</div>
</div>
<div role="tabpanel" id="tabs-rtl-panel-reports" aria-labelledby="tabs-rtl-tab-reports" tabindex="-1" hidden>
<div class="card">
<header>
<h2>التقارير</h2>
<p>إنشاء وتنزيل تقاريرك التفصيلية. تصدير البيانات بتنسيقات متعددة للتحليل.</p>
</header>
<section>
<p class="text-muted-foreground text-sm">لديك ٥ تقارير جاهزة ومتاحة للتصدير.</p>
</section>
</div>
</div>
<div role="tabpanel" id="tabs-rtl-panel-settings" aria-labelledby="tabs-rtl-tab-settings" tabindex="-1" hidden>
<div class="card">
<header>
<h2>الإعدادات</h2>
<p>إدارة تفضيلات حسابك وخياراته. تخصيص تجربتك لتناسب احتياجاتك.</p>
</header>
<section>
<p class="text-muted-foreground text-sm">تكوين الإشعارات والأمان والسمات.</p>
</section>
</div>
</div>
</div>
</Preview>
+108
View File
@@ -0,0 +1,108 @@
# Textarea
<Preview class="w-full max-w-xs">
<textarea class="textarea" placeholder="Type your message here"></textarea>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Textarea component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/textarea.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your textarea HTML">
Add `class="textarea"` to a `<textarea>` element. Textareas inside a `class="field"` container also receive the same styling automatically.
Use native HTML attributes for behavior: `disabled` for disabled textareas and `aria-invalid="true"` for invalid state styling.
```html
<textarea class="textarea" placeholder="Type your message here"></textarea>
```
</Step>
</Steps>
Textareas support right-to-left layouts through native browser direction handling.
## Examples
### Field
Use `field` with a label and description to create an accessible textarea.
<Preview class="w-full max-w-xs">
<div role="group" class="field">
<label for="textarea-field">Message</label>
<textarea id="textarea-field" placeholder="Type your message here"></textarea>
<p>Write a short message.</p>
</div>
</Preview>
### Disabled
<Preview class="w-full max-w-xs">
<textarea class="textarea" placeholder="Type your message here" disabled></textarea>
</Preview>
### Invalid
<Preview class="w-full max-w-xs">
<div role="group" class="field w-full max-w-xs">
<label for="textarea-invalid">Message</label>
<textarea class="textarea" id="textarea-invalid" placeholder="Type your message here" aria-invalid="true"></textarea>
<p>Message is required.</p>
</div>
</Preview>
### Button
<Preview class="w-full max-w-xs">
<div class="grid w-full max-w-xs gap-3">
<textarea class="textarea" placeholder="Type your message here"></textarea>
<button type="submit" class="btn">Submit</button>
</div>
</Preview>
### Form
<Preview class="w-full max-w-sm">
<form class="w-full max-w-sm space-y-6">
<div role="group" class="field">
<label for="textarea-form">Bio</label>
<textarea id="textarea-form" placeholder="Tell us a bit about yourself"></textarea>
<p>You can @mention other users and organizations.</p>
</div>
<button type="submit" class="btn">Submit</button>
</form>
</Preview>
### RTL
Add `dir="rtl"` to the textarea, field, or an ancestor.
<Preview class="w-full max-w-xs">
<div role="group" class="field w-full max-w-xs" dir="rtl">
<label for="textarea-rtl">التعليقات</label>
<textarea id="textarea-rtl" placeholder="تعليقاتك تساعدنا على التحسين..." rows="4"></textarea>
<p>شاركنا أفكارك حول الخدمة.</p>
</div>
</Preview>
@@ -0,0 +1,97 @@
# Theme Switcher
<Preview>
<button
type="button"
aria-label="Toggle dark mode"
data-tooltip="Toggle dark mode"
data-side="bottom"
onclick="window.basecoat.theme.toggle()"
class="btn size-8" data-variant="outline" data-size="icon"
>
<span class="hidden dark:block"><svg class="lucide lucide-sun" 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"><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg></span>
<span class="block dark:hidden"><svg class="lucide lucide-moon" 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"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></svg></span>
</button>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, the component CSS files it composes, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/button.css";
@import "basecoat-css/components/tooltip.css";
@import "basecoat-css/styles/vega.css";
```
Theme Switcher composes Button and Tooltip component CSS.
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime only.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Avoid the initial flash">
Add this inline script before loading your styles so the stored mode is applied before the page renders.
```html
<script>
(() => {
try {
const stored = localStorage.getItem("themeMode");
if (stored ? stored === "dark" : matchMedia("(prefers-color-scheme: dark)").matches) {
document.documentElement.classList.add("dark");
}
} catch (_) {}
})();
</script>
```
</Step>
<Step title="Add your switcher HTML">
```html
<button type="button" aria-label="Toggle dark mode" data-tooltip="Toggle dark mode" data-side="bottom" onclick="window.basecoat.theme.toggle()" class="btn size-8" data-variant="outline" data-size="icon">
<span class="hidden dark:block"><svg class="lucide lucide-sun" 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"><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="m4.93 4.93 1.41 1.41" /><path d="m17.66 17.66 1.41 1.41" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="m6.34 17.66-1.41 1.41" /><path d="m19.07 4.93-1.41 1.41" /></svg></span>
<span class="block dark:hidden"><svg class="lucide lucide-moon" 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"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" /></svg></span>
</button>
```
</Step>
</Steps>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `window.basecoat.theme.get()` | Method | Returns `"dark"` or `"light"`. |
| `window.basecoat.theme.set(mode)` | Method | Sets the mode to `"dark"` or `"light"` and stores it in `localStorage.themeMode`. |
| `window.basecoat.theme.toggle()` | Method | Toggles between dark and light mode. |
+179
View File
@@ -0,0 +1,179 @@
# Toast
<div id="toaster" class="toaster"></div>
<Preview class="max-w-md">
<button
class="btn" data-variant="outline"
hx-trigger="click"
hx-select="unset"
hx-get="/fragments/toast/success"
hx-target="#toaster"
hx-swap="beforeend"
>Toast from backend (with HTMX)</button>
</Preview>
<Preview class="max-w-md">
<button
class="btn" data-variant="outline"
onclick="document.getElementById('toaster').toast({
category: 'success',
title: 'Success',
description: 'A success toast called from the front-end.',
cancel: {
label: 'Dismiss'
}
})"
>Toast from front-end</button>
</Preview>
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#toast" }}>
This component ships `toaster()` and `toast()` macros for Jinja and Nunjucks.
</Callout>
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Toast component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/toast.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Include JavaScript">
Copy or serve the full Basecoat JavaScript bundle.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or copy or serve the Basecoat runtime and Toast script.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/toast.min.js" defer></script>
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add the toaster HTML">
Add one toaster near the end of your page body:
```html
<div id="toaster" class="toaster"></div>
```
Set `data-align="start"`, `data-align="center"`, or `data-align="end"` on the toaster to change placement.
</Step>
<Step title="Add your toasts">
Append server-rendered toast markup to the toaster, or create one from JavaScript with `toaster.toast(config)`.
```html
<div id="toaster" class="toaster">
<div class="toast" role="status" aria-atomic="true" aria-hidden="false" data-category="success">
<div class="toast-content"><svg aria-hidden="true" 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"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
<section>
<h2>Success</h2>
<p>A success toast called from the front-end.</p>
</section>
<footer>
<button type="button" class="btn" data-toast-action>Dismiss</button>
</footer>
</div>
</div>
</div>
```
```html
<button
class="btn" data-variant="outline"
onclick="document.getElementById('toaster').toast({
category: 'success',
title: 'Success',
description: 'A success toast called from the front-end.',
cancel: {
label: 'Dismiss'
}
})">Toast from front-end</button>
```
</Step>
</Steps>
### HTML structure
<dl>
<dt><code>&lt;div id="toaster" class="toaster"&gt;</code></dt>
<dd>Toast container. Optional <code>data-align</code> values are <code>start</code>, <code>center</code>, and <code>end</code>.</dd>
<dt><code>&lt;div class="toast"&gt;</code></dt>
<dd>
Toast item. Use <code>data-category="success|info|warning|error"</code> for category styling and <code>data-duration</code> to override the timeout in milliseconds. Use <code>data-duration="-1"</code> to keep it open.
<dl>
<dt><code>&lt;div class="toast-content"&gt;</code></dt>
<dd>
Content wrapper.
<dl>
<dt><code>&lt;svg aria-hidden="true"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Icon.</dd>
<dt><code>&lt;section&gt;</code></dt>
<dd>
Message region.
<dl>
<dt><code>&lt;h2&gt;</code></dt>
<dd>Title.</dd>
<dt><code>&lt;p&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Description.</dd>
</dl>
</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Action buttons or links. Clicking a footer button or link closes the toast.</dd>
</dl>
</dd>
</dl>
</dd>
</dl>
### JavaScript API
| API | Type | Description |
| --- | --- | --- |
| `toaster.toast(config)` | Method | Creates a toast from a config object and returns the inserted `.toast` element. [See "Config object" below](#config-object). |
| `toaster.closeAll()` | Method | Closes every toast inside the toaster. |
| `toast.close()` | Method | Closes one initialized toast element. |
### Config object
<dl>
<dt><code>duration</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Timeout in milliseconds. Defaults to <code>3000</code>, or <code>5000</code> for error toasts. Use <code>-1</code> to keep it open.</dd>
<dt><code>category</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Category of the toast, either <code>success</code>, <code>info</code>, <code>warning</code>, or <code>error</code>.</dd>
<dt><code>title</code></dt>
<dd>The title of the toast.</dd>
<dt><code>description</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The description of the toast.</dd>
<dt><code>action</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Primary action. Supports <code>label</code> with either <code>href</code> or <code>onclick</code>.</dd>
<dt><code>cancel</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Secondary action. Supports <code>label</code> and optional <code>onclick</code>.</dd>
</dl>
+92
View File
@@ -0,0 +1,92 @@
# Tooltip
<Preview>
<button class="btn" data-variant="outline" data-tooltip="Add to library">Hover</button>
</Preview>
## Usage
<Steps>
<Step title="Include CSS">
Import Tailwind and one full Basecoat style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/vega.css";
```
Or import only the base CSS, Tooltip component CSS, and one style pack.
```css
@import "tailwindcss";
@import "basecoat-css/base.css";
@import "basecoat-css/components/tooltip.css";
@import "basecoat-css/styles/vega.css";
```
Using CDN or bundler imports? See the [Installation page](/installation).
</Step>
<Step title="Add your tooltip HTML">
Add `data-tooltip` to the trigger element. Use `data-side` and `data-align` to change the position:
- `data-tooltip`: Tooltip text.
- `data-side` `Optional`: `top`, `bottom`, `left`, `right`, `inline-start`, or `inline-end`. Defaults to `top`.
- `data-align` `Optional`: `start`, `center`, or `end`. Defaults to `center`.
Basecoat tooltips are CSS-only and text-only. This intentionally differs from shadcn/ui's composed tooltip content, which can render arbitrary markup and uses a portal.
```html
<button class="btn" data-variant="outline" data-tooltip="Tooltip text" data-side="bottom" data-align="center">Bottom</button>
```
</Step>
</Steps>
## Examples
### Default
<Preview>
<button class="btn" data-variant="outline" data-tooltip="Default tooltip">Default</button>
</Preview>
### Sides
<Preview>
<div class="flex flex-wrap gap-2">
<button class="btn" data-variant="outline" data-tooltip="Tooltip" data-side="inline-start">Inline Start</button>
<button class="btn" data-variant="outline" data-tooltip="Tooltip" data-side="top">Top</button>
<button class="btn" data-variant="outline" data-tooltip="Tooltip" data-side="bottom">Bottom</button>
<button class="btn" data-variant="outline" data-tooltip="Tooltip" data-side="inline-end">Inline End</button>
</div>
</Preview>
### Icon button
<Preview>
<button class="btn" data-variant="ghost" data-size="icon" data-tooltip="Additional information" aria-label="Info"><svg class="lucide lucide-info" 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"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg></button>
</Preview>
### Disabled button
<Preview>
<span class="inline-block" data-tooltip="This feature is currently unavailable"> <button class="btn" data-variant="outline" disabled>Disabled</button></span>
</Preview>
### Link
<Preview>
<a href="#" class="text-primary text-sm underline-offset-4 hover:underline" data-tooltip="Click to read the documentation">Learn more</a>
</Preview>
### RTL
<Preview>
<div dir="rtl" class="flex gap-2">
<button class="btn" data-variant="outline" data-tooltip="تلميح" data-side="inline-start">Inline Start</button>
<button class="btn" data-variant="outline" data-tooltip="تلميح" data-side="inline-end">Inline End</button>
</div>
</Preview>
+145
View File
@@ -0,0 +1,145 @@
# Customization
Basecoat customization has three layers:
1. Choose a style pack.
2. Override theme tokens such as colors and fonts.
3. Add small project-level CSS overrides when tokens are not enough.
For a full visual rewrite, import the Basecoat base layer and write your own style pack instead of overriding an existing one wholesale.
## Styles
Basecoat ships the same style families used by the current shadcn/ui registry: Vega, Nova, Maia, Lyra, Mira, Luma, Sera, and Rhea.
Pick one complete style bundle in your app CSS:
```css
@import "tailwindcss";
@import "basecoat-css/vega";
```
Swap the import to use another style:
```css
@import "tailwindcss";
@import "basecoat-css/sera";
```
Each style bundle is standalone. Do not import Vega first and then load another style on top of it.
Keep the selected Basecoat style after Tailwind or any other stylesheet that emits a reset/base layer.
## Themes
Basecoat uses shadcn/ui-compatible CSS variables. You can use a theme from [TweakCN](https://tweakcn.com) or another shadcn/ui theme generator by importing the variables after Basecoat:
```css
@import "tailwindcss";
@import "basecoat-css/sera";
@import "./theme.css";
```
`theme.css` should define token values such as `--background`, `--foreground`, `--primary`, `--muted`, `--border`, `--input`, and `--ring` for `:root` and `.dark` when needed.
Learn more in the [shadcn/ui theming docs](https://ui.shadcn.com/docs/theming).
## Fonts
Basecoat does not ship web font files by default. Its font tokens prefer [Geist Sans](https://fonts.google.com/specimen/Geist) and [Geist Mono](https://fonts.google.com/specimen/Geist+Mono) when those fonts are available, then fall back to the full Tailwind default sans and mono stacks.
Install the fonts with Fontsource:
```bash
npm install @fontsource/geist-sans @fontsource/geist-mono
```
Then import the font files. Basecoat's default font tokens already reference Geist with full system fallbacks:
```css
@import "tailwindcss";
@import "basecoat-css/sera";
@import "@fontsource/geist-sans/400.css";
@import "@fontsource/geist-sans/500.css";
@import "@fontsource/geist-sans/600.css";
@import "@fontsource/geist-sans/700.css";
@import "@fontsource/geist-mono/400.css";
@import "@fontsource/geist-mono/500.css";
@import "@fontsource/geist-mono/600.css";
@import "@fontsource/geist-mono/700.css";
```
If you use different fonts, override the font tokens after Basecoat:
```css
@import "tailwindcss";
@import "basecoat-css/sera";
:root {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-heading: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
```
Keep font overrides in a small CSS file imported after Basecoat. Fonts are project-level customization, not a Basecoat default.
## Icons
Basecoat examples use [Lucide icons](https://lucide.dev), but Basecoat does not require an icon package.
You can:
- Copy SVGs from [lucide.dev/icons](https://lucide.dev/icons) and paste them into your HTML.
- Install [`lucide`](https://lucide.dev/guide/installation) and render icons from JavaScript.
- Use a framework-specific Lucide package if your app already uses a framework.
Inline SVG is the simplest option for plain HTML, Jinja, Nunjucks, Rails, Django, Laravel, or similar stacks.
## Project overrides
For small visual tweaks, add utilities or project CSS after Basecoat:
```css
@import "tailwindcss";
@import "basecoat-css/sera";
@import "./app.css";
```
```html
<button class="btn font-normal" data-variant="outline">Click me</button>
```
Prefer theme tokens for broad changes and one-off utilities for local changes. Avoid copying generated Basecoat bundles into your app unless you intend to maintain a fork.
## Custom style packs
If you want a full custom style, import the styleless Basecoat base and then your own style file:
```css
@import "tailwindcss";
@import "basecoat-css/base";
@import "./styles/acme.css";
```
`basecoat-css/base` includes Basecoat tokens, semantic utilities, and component structure, but no Vega/Nova/Maia/etc. visual style pack. Your style file owns component visuals: colors, radius, shadows, focus rings, spacing, variants, and state treatment.
The practical way to start is to copy one existing style pack from `basecoat-css/styles/*`, rename it, and edit it:
```css
@import "basecoat-css/base";
@import "./styles/acme.css";
/* ./styles/acme.css can start as a copy of basecoat-css/styles/nova. */
```
Do not import a complete style bundle before your custom style. For example, avoid importing `basecoat-css/nova` and then overriding it wholesale. That loads Nova visuals and forces your file to undo them. Use `basecoat-css/base` instead.
## Compatibility
Basecoat 1.0 prefers `data-variant` and `data-size` over legacy variant classes. If you need old pre-1.0 aliases while migrating, import the compatibility layer after Basecoat:
```css
@import "tailwindcss";
@import "basecoat-css/vega";
@import "basecoat-css/compat";
```
The compatibility layer is for migration only. New code should use the documented 1.0 API.
+111
View File
@@ -0,0 +1,111 @@
{
"menu": [
{
"type": "group",
"label": "Getting started",
"items": [
{
"type": "item",
"label": "Introduction",
"slug": "introduction",
"icon": "info"
},
{
"type": "item",
"label": "Installation",
"slug": "installation",
"icon": "download"
},
{
"type": "item",
"label": "Customize",
"slug": "customization",
"icon": "sliders-horizontal"
},
{
"type": "item",
"label": "Templates",
"slug": "templates",
"icon": "code"
},
{
"type": "item",
"label": "GitHub",
"url": "https://github.com/hunvreus/basecoat",
"icon": "<svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"/></svg>",
"external": true,
"attrs": {
"target": "_blank",
"rel": "noopener"
}
},
{
"type": "item",
"label": "Discord",
"url": "https://basecoatui.com/chat",
"icon": "<svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z\"/></svg>",
"external": true,
"attrs": {
"target": "_blank",
"rel": "noopener"
}
}
]
},
{
"type": "group",
"label": "Components",
"items": [
"components/accordion",
"components/alert",
"components/alert-dialog",
"components/avatar",
"components/badge",
"components/breadcrumb",
"components/button",
"components/button-group",
"components/card",
{
"type": "item",
"slug": "components/chart",
"badge": "Beta"
},
"components/checkbox",
"components/combobox",
"components/command",
"components/dialog",
{
"type": "item",
"slug": "components/drawer",
"badge": "Beta"
},
"components/dropdown-menu",
"components/empty",
"components/field",
"components/input",
"components/input-group",
"components/item",
"components/kbd",
"components/label",
"components/native-select",
"components/pagination",
"components/popover",
"components/progress",
"components/radio-group",
"components/select",
"components/scroll-area",
"components/sidebar",
"components/skeleton",
"components/slider",
"components/spinner",
"components/switch",
"components/table",
"components/tabs",
"components/textarea",
"components/theme-switcher",
"components/toast",
"components/tooltip"
]
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{% from "toast.njk" import toast %}
{{ toast(
category="error",
title="Event has not been created",
cancel={ label: "Dismiss", click: "close()" }
) }}
+5
View File
@@ -0,0 +1,5 @@
{% from "toast.njk" import toast %}
{{ toast(
category="info",
title="Be at the area 10 minutes before the event time"
) }}
@@ -0,0 +1,7 @@
{% from "toast.njk" import toast %}
{{ toast(
title="Event has been created",
description="Sunday, December 03, 2023 at 9:00 AM",
action={ label: "View" },
cancel={ label: "Undo" }
) }}
@@ -0,0 +1,5 @@
{% from "toast.njk" import toast %}
{{ toast(
category="warning",
title="Event start time cannot be earlier than 8am"
) }}
+256
View File
@@ -0,0 +1,256 @@
import packageJson from "../../packages/css/package.json"
export const basecoatVersion = packageJson.version
# Installation
Basecoat can be used from a CDN or installed with npm. Use the CDN for simple static pages and prototypes. Use npm when your project already has a Tailwind CSS build step.
## CDN
<Steps>
<Step title="Add the stylesheet">
Use the default bundled stylesheet. It is an alias for the Vega style bundle.
<Code lang="html" code={`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@${basecoatVersion}/dist/basecoat.cdn.min.css" />`} />
You can also use one of the named style bundles: `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, or `rhea`.
<Code lang="html" code={`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@${basecoatVersion}/dist/basecoat-maia.cdn.min.css" />`} />
</Step>
<Step title="Add JavaScript when needed">
Most components are CSS-only. Interactive components need JavaScript. Use the all-in-one bundle when file size is not a concern.
<Code lang="html" code={`<script src="https://cdn.jsdelivr.net/npm/basecoat-css@${basecoatVersion}/dist/js/all.min.js" defer></script>`} />
Or load the Basecoat runtime once, then only the component scripts you use.
<Code
lang="html"
code={`<script src="https://cdn.jsdelivr.net/npm/basecoat-css@${basecoatVersion}/dist/js/basecoat.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@${basecoatVersion}/dist/js/dropdown-menu.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@${basecoatVersion}/dist/js/select.min.js" defer></script>`}
/>
</Step>
<Step title="Use the markup">
```html
<button class="btn">Button</button>
<div class="card">
<header>
<h3>Card title</h3>
<p>Card description.</p>
</header>
<section>Card content.</section>
</div>
```
</Step>
</Steps>
<Callout title="Icons are not bundled" icon="info">
Basecoat examples use inline Lucide SVGs, but Basecoat does not ship an icon package. Copy SVGs into your markup or install your preferred icon library. See [Customization > Icons](/customization#icons).
</Callout>
### CDN files
| File | Description |
| --- | --- |
| `dist/basecoat.cdn.min.css` | Default standalone CDN stylesheet. Alias for the Vega style. |
| `dist/basecoat-{style}.cdn.min.css` | Standalone CDN stylesheet for `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, or `rhea`. |
| `dist/basecoat-base.cdn.min.css` | Base tokens, utilities, and component structure without a style pack. |
| `dist/basecoat-compat.cdn.min.css` | Optional pre-1.0 compatibility aliases. Load after Basecoat. |
| `dist/js/all.min.js` | All auto-initialized JavaScript components except Chart. |
| `dist/js/basecoat.min.js` | Runtime only. Load once before individual component scripts. |
| `dist/js/{component}.min.js` | Individual JavaScript component file. |
## NPM
<Steps>
<Step title="Install Tailwind CSS">
Basecoat is authored for Tailwind CSS. Install Tailwind first if your project does not already use it.
[Follow the Tailwind CSS installation guide](https://tailwindcss.com/docs/installation).
</Step>
<Step title="Install Basecoat">
<CodeGroup>
```bash title="npm"
npm install basecoat-css
```
```bash title="pnpm"
pnpm add basecoat-css
```
```bash title="bun"
bun add basecoat-css
```
```bash title="yarn"
yarn add basecoat-css
```
</CodeGroup>
If you are not using a bundler, copy or serve the files you need from `node_modules/basecoat-css/dist`.
</Step>
<Step title="Add CSS">
Include one Basecoat stylesheet after your Tailwind CSS. The default stylesheet is an alias for the Vega style bundle.
```html
<link rel="stylesheet" href="/assets/basecoat.css" />
```
You can also use one of the named style bundles: `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, or `rhea`.
```html
<link rel="stylesheet" href="/assets/basecoat-maia.css" />
```
If your build tool supports CSS package imports, import Tailwind and then one Basecoat bundle.
```css
@import "tailwindcss";
@import "basecoat-css";
```
Or import a named style bundle.
```css
@import "tailwindcss";
@import "basecoat-css/maia";
```
<Callout type="warning" title="Stylesheet order matters" icon="triangle-alert">
Load Basecoat after any stylesheet that includes Tailwind base/preflight. Loading another full Tailwind build after Basecoat can reset borders, inputs, and other component defaults.
</Callout>
</Step>
<Step title="Add JavaScript when needed">
Most components are CSS-only. Interactive components need JavaScript. Include the all-in-one script when file size is not a concern.
```html
<script src="/assets/js/all.min.js" defer></script>
```
Or include the Basecoat runtime once, then only the component scripts you use.
```html
<script src="/assets/js/basecoat.min.js" defer></script>
<script src="/assets/js/dropdown-menu.min.js" defer></script>
<script src="/assets/js/select.min.js" defer></script>
```
If your build tool supports JavaScript package imports, use the all-in-one import:
```js
import "basecoat-css/all";
```
Or import the runtime once, then individual components.
```js
import "basecoat-css/basecoat";
import "basecoat-css/dropdown-menu";
import "basecoat-css/select";
```
</Step>
</Steps>
### Templates
Basecoat ships optional Nunjucks and Jinja templates with `basecoat-css`.
If you previously used `basecoat-cli`, install `basecoat-css` instead and copy the template files from the package.
<CodeGroup>
```bash title="Nunjucks"
cp -R node_modules/basecoat-css/templates/nunjucks ./templates/basecoat
```
```bash title="Jinja"
cp -R node_modules/basecoat-css/templates/jinja ./templates/basecoat
```
</CodeGroup>
See [Templates](/templates) for the available macros and arguments.
### CSS imports
| Import | Description |
| --- | --- |
| `basecoat-css` | Default full stylesheet. Alias for `basecoat-css/vega`. |
| `basecoat-css/{style}` | Full stylesheet for `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, or `rhea`. |
| `basecoat-css/base` | Base tokens, utilities, and component structure without a style pack. Use this before a custom style. |
| `basecoat-css/components` | All component structure without a style pack. |
| `basecoat-css/components/button` | Individual component CSS. Replace `button` with any component file. |
| `basecoat-css/styles/maia` | Individual style-pack CSS. Use with `basecoat-css/base` or `basecoat-css/components`. |
| `basecoat-css/all.css` | Full default stylesheet plus all style-pack CSS. Use only when you need every style pack available at runtime. |
| `basecoat-css/compat` | Optional pre-1.0 compatibility aliases. Load after Basecoat. |
### Template files
| Path | Description |
| --- | --- |
| `node_modules/basecoat-css/templates/nunjucks` | Nunjucks templates. |
| `node_modules/basecoat-css/templates/jinja` | Jinja templates. |
### JavaScript imports
| Import | Description |
| --- | --- |
| `basecoat-css/all` | All auto-initialized JavaScript components except Chart. |
| `basecoat-css/basecoat` | Runtime only. Import once before individual component scripts. |
| `basecoat-css/chart` | Chart helper. Load Chart.js separately. |
| `basecoat-css/combobox` | Combobox controller. |
| `basecoat-css/command` | Command controller. |
| `basecoat-css/drawer` | Drawer controller. |
| `basecoat-css/dropdown-menu` | Dropdown Menu controller. |
| `basecoat-css/popover` | Popover controller. |
| `basecoat-css/range` | Slider/range controller. |
| `basecoat-css/select` | Select controller. |
| `basecoat-css/sidebar` | Sidebar controller. |
| `basecoat-css/tabs` | Tabs controller. |
| `basecoat-css/toast` | Toast controller. |
## JavaScript components
These components need JavaScript for behavior:
- [Accordion](/components/accordion)
- [Combobox](/components/combobox)
- [Command](/components/command)
- [Drawer](/components/drawer)
- [Dropdown Menu](/components/dropdown-menu)
- [Popover](/components/popover)
- [Select](/components/select)
- [Sidebar](/components/sidebar)
- [Slider](/components/slider)
- [Tabs](/components/tabs)
- [Toast](/components/toast)
Chart uses a separate helper and is not included in `basecoat-css/all`.
Each component page shows the minimal scripts or imports required for that component.
## Compatibility
Basecoat 1.0 introduces new style packs and a smaller public API based on root classes plus semantic HTML and documented attributes. If you need pre-1.0 class aliases while migrating, load the compatibility stylesheet after your Basecoat stylesheet.
```css
@import "tailwindcss";
@import "basecoat-css";
@import "basecoat-css/compat";
```
The compatibility stylesheet is intended for the default Basecoat style. It is not a separate style pack.
+42
View File
@@ -0,0 +1,42 @@
# Introduction
## Why Basecoat?
**Basecoat brings the magic of [shadcn/ui](https://ui.shadcn.com/) to any traditional web stack: no React required.**
Tailwind won. But building UIs with utility classes alone kinda sucks. Most Tailwind libraries like Flowbite, Preline, or even Tailwind UI ask you to copy walls of unreadable classes into your HTML. It works, but it's messy and hard to maintain.
shadcn/ui avoids that by wrapping everything in React components. It also gives you a killer design system, theme support, and a growing ecosystem.
But maybe you're not using React. Maybe your app is built with plain HTML. Or Flask. Or Rails. Or Laravel. Or Django. Or whatever.
**That's where Basecoat comes in.** It gives you modern, accessible components with the simplicity of plain HTML and Tailwind. Basecoat is:
- **Lightweight**: no runtime JS, just CSS and a tiny bit of vanilla JavaScript for the more interactive components.
- **Easy to use**: add classes like `btn` or `input` and you're done.
- **Framework-agnostic**: works with any backend or frontend stack.
- **Accessible**: components follow accessibility best practices.
- **Dark mode ready**: respects your Tailwind config.
- **Extendable**: tweak styles with Tailwind or CSS variables.
- **Themable**: fully compatible with shadcn/ui themes.
- **Readable**: no class soup, just clean markup.
- **Free and open source**: MIT licensed.
## How it works
Add a single CSS file to your Tailwind setup. Use components by dropping in simple classes like `btn`, `field`, or `card`.
Some components (like modals or dropdowns) use a tiny bit of vanilla JavaScript. You can skip it if you don't need interactivity.
Optional Nunjucks and Jinja templates ship with the package for server-rendered apps.
[Check out the installation guide](/installation) to get started.
## How can I help?
Basecoat is 100% open source and free.
- [Star it on GitHub](https://github.com/hunvreus/basecoat)
- [Report bugs or request features](https://github.com/hunvreus/basecoat/issues)
- [Submit a pull request](https://github.com/hunvreus/basecoat/pulls)
- [Sponsor the project](https://github.com/sponsors/hunvreus)
+119
View File
@@ -0,0 +1,119 @@
---
import vegaUrl from "../assets/styles/style-vega.css?url";
import novaUrl from "../assets/styles/style-nova.css?url";
import maiaUrl from "../assets/styles/style-maia.css?url";
import lyraUrl from "../assets/styles/style-lyra.css?url";
import miraUrl from "../assets/styles/style-mira.css?url";
import lumaUrl from "../assets/styles/style-luma.css?url";
import seraUrl from "../assets/styles/style-sera.css?url";
import rheaUrl from "../assets/styles/style-rhea.css?url";
const styleUrls = {
vega: vegaUrl,
nova: novaUrl,
maia: maiaUrl,
lyra: lyraUrl,
mira: miraUrl,
luma: lumaUrl,
sera: seraUrl,
rhea: rheaUrl,
};
---
<nav class="hidden items-center gap-2 sm:flex">
<select id="style-variant-select" class="select h-8 w-28 text-sm leading-none" aria-label="Style pack">
<option value="vega">Vega</option>
<option value="nova">Nova</option>
<option value="maia">Maia</option>
<option value="lyra">Lyra</option>
<option value="mira">Mira</option>
<option value="luma">Luma</option>
<option value="sera">Sera</option>
<option value="rhea">Rhea</option>
</select>
</nav>
<script is:inline define:vars={{ styleUrls }}>
(() => {
const styleSelect = document.getElementById("style-variant-select");
const styleLinkId = "docs-style-variant";
const pendingStyleLinkId = "docs-style-variant-pending";
const styleGateId = "docs-style-gate";
const getStyleLink = () => document.getElementById(styleLinkId);
const clearStylePending = () => {
document.documentElement.removeAttribute("data-style-pending");
document.getElementById(styleGateId)?.remove();
};
const getStyleHref = (style) => {
const href = styleUrls[style];
return href?.startsWith("/docs/src/assets/styles/") ? `${href}?direct` : href;
};
const applyStyle = (style) => {
const nextStyle = Object.prototype.hasOwnProperty.call(styleUrls, style) ? style : "vega";
const href = getStyleHref(nextStyle);
const link = getStyleLink();
if (!href) {
link?.remove();
document.getElementById(pendingStyleLinkId)?.remove();
clearStylePending();
return;
}
const sameHref = link?.getAttribute("href") === href;
if (sameHref || link?.sheet) {
clearStylePending();
}
if (sameHref) return;
document.getElementById(pendingStyleLinkId)?.remove();
const pendingLink = document.createElement("link");
pendingLink.id = pendingStyleLinkId;
pendingLink.rel = "stylesheet";
pendingLink.addEventListener(
"load",
() => {
link?.remove();
pendingLink.id = styleLinkId;
document.documentElement.dataset.styleVariant = nextStyle;
clearStylePending();
document.dispatchEvent(new CustomEvent("basecoat:stylechange", { detail: { style: nextStyle } }));
},
{ once: true },
);
pendingLink.addEventListener(
"error",
() => {
pendingLink.remove();
clearStylePending();
},
{ once: true },
);
pendingLink.href = href;
document.head.appendChild(pendingLink);
};
const getStoredStyle = () => {
try {
return localStorage.getItem("styleVariant") || "vega";
} catch (_) {
return "vega";
}
};
const initialStyle = getStoredStyle();
if (styleSelect) styleSelect.value = initialStyle;
applyStyle(initialStyle);
styleSelect?.addEventListener("change", () => {
const nextStyle = styleSelect.value || "vega";
applyStyle(nextStyle);
try {
localStorage.setItem("styleVariant", nextStyle);
} catch (_) {}
});
})();
</script>
+28
View File
@@ -0,0 +1,28 @@
---
const { site } = Astro.props;
const escapeHtml = (value) =>
String(value ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;");
const logo = site.logo?.svg
? site.logo.svg
: site.logo?.url
? `<img src="${escapeHtml(site.logo.url)}" alt="${escapeHtml(site.title)} logo" class="size-6" />`
: "";
---
<a href="/" class="btn h-12 w-full justify-start p-2" data-variant="ghost" data-size="lg" hx-boost="false">
{
logo && (
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<Fragment set:html={logo} />
</div>
)
}
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{site.title}</span>
{site.subtitle && <span class="truncate text-xs text-muted-foreground">{site.subtitle}</span>}
</div>
</a>
+172
View File
@@ -0,0 +1,172 @@
---
import vegaUrl from "../assets/styles/style-vega.css?url";
import novaUrl from "../assets/styles/style-nova.css?url";
import maiaUrl from "../assets/styles/style-maia.css?url";
import lyraUrl from "../assets/styles/style-lyra.css?url";
import miraUrl from "../assets/styles/style-mira.css?url";
import lumaUrl from "../assets/styles/style-luma.css?url";
import seraUrl from "../assets/styles/style-sera.css?url";
import rheaUrl from "../assets/styles/style-rhea.css?url";
import demoInteractionsUrl from "./demo-interactions.js?url";
import htmxUrl from "htmx.org/dist/htmx.min.js?url";
const styleUrls = {
vega: vegaUrl,
nova: novaUrl,
maia: maiaUrl,
lyra: lyraUrl,
mira: miraUrl,
luma: lumaUrl,
sera: seraUrl,
rhea: rheaUrl,
};
---
<style is:inline>
@font-face {
font-family: "Geist Sans";
src: url("/assets/fonts/geist/geist-sans-latin-400-normal.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Sans";
src: url("/assets/fonts/geist/geist-sans-latin-500-normal.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Sans";
src: url("/assets/fonts/geist/geist-sans-latin-600-normal.woff2") format("woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Sans";
src: url("/assets/fonts/geist/geist-sans-latin-700-normal.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Mono";
src: url("/assets/fonts/geist/geist-mono-latin-400-normal.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Mono";
src: url("/assets/fonts/geist/geist-mono-latin-500-normal.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Mono";
src: url("/assets/fonts/geist/geist-mono-latin-600-normal.woff2") format("woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Geist Mono";
src: url("/assets/fonts/geist/geist-mono-latin-700-normal.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
</style>
<style id="docs-style-gate" is:inline>
html[data-style-pending] body {
visibility: hidden;
}
</style>
<script is:inline define:vars={{ styleUrls }}>
(() => {
const styleGateId = "docs-style-gate";
const styleLinkId = "docs-style-variant";
const clearStylePending = () => {
document.documentElement.removeAttribute("data-style-pending");
document.getElementById(styleGateId)?.remove();
};
const getStoredStyle = () => {
try {
return localStorage.getItem("styleVariant") || "vega";
} catch (_) {
return "vega";
}
};
const getStyleHref = (style) => {
const href = styleUrls[style];
return href?.startsWith("/docs/src/assets/styles/") ? `${href}?direct` : href;
};
const style = getStoredStyle();
const nextStyle = Object.prototype.hasOwnProperty.call(styleUrls, style) ? style : "vega";
const href = getStyleHref(nextStyle);
document.documentElement.dataset.styleVariant = nextStyle;
if (!href) return;
document.documentElement.setAttribute("data-style-pending", "");
const existing = document.getElementById("docs-style-variant");
if (existing?.getAttribute("href") === href) {
clearStylePending();
return;
}
if (!existing && document.currentScript) {
document.write(
`<link id="${styleLinkId}" rel="stylesheet" href="${href}" onload="document.documentElement.removeAttribute('data-style-pending');document.getElementById('${styleGateId}')?.remove()" onerror="document.documentElement.removeAttribute('data-style-pending');document.getElementById('${styleGateId}')?.remove()">`,
);
return;
}
const link = existing || document.createElement("link");
link.id = styleLinkId;
link.rel = "stylesheet";
link.addEventListener("load", clearStylePending, { once: true });
link.addEventListener("error", clearStylePending, { once: true });
link.href = href;
if (!existing) document.head.appendChild(link);
})();
</script>
<script is:inline>
(() => {
const key = (href) => {
const url = new URL(href, window.location.origin);
return `${url.pathname.replace(/\/$/, "")}${url.search}`;
};
const syncSidebar = () => {
for (const link of document.querySelectorAll("#sidebar a[href]")) {
const isCurrent = key(link.href) === key(window.location.href);
if (isCurrent) {
link.setAttribute("aria-current", "page");
link.closest("details")?.setAttribute("open", "");
} else {
link.removeAttribute("aria-current");
}
}
};
document.addEventListener("DOMContentLoaded", syncSidebar);
document.addEventListener("htmx:afterSettle", () => {
window.basecoat?.initAll?.();
syncSidebar();
});
window.addEventListener("popstate", syncSidebar);
})();
</script>
<script src={htmxUrl} defer></script>
<script type="module" src={demoInteractionsUrl}></script>
+245
View File
@@ -0,0 +1,245 @@
const visitorsByDay = [
{ date: "Apr 1", desktop: 222, mobile: 150 },
{ date: "Apr 5", desktop: 373, mobile: 290 },
{ date: "Apr 9", desktop: 59, mobile: 110 },
{ date: "Apr 13", desktop: 342, mobile: 380 },
{ date: "Apr 17", desktop: 446, mobile: 360 },
{ date: "Apr 21", desktop: 137, mobile: 200 },
{ date: "Apr 25", desktop: 215, mobile: 250 },
{ date: "Apr 29", desktop: 315, mobile: 240 },
{ date: "May 3", desktop: 247, mobile: 190 },
{ date: "May 7", desktop: 388, mobile: 300 },
{ date: "May 11", desktop: 335, mobile: 270 },
{ date: "May 15", desktop: 473, mobile: 380 },
{ date: "May 19", desktop: 235, mobile: 180 },
{ date: "May 23", desktop: 252, mobile: 290 },
{ date: "May 27", desktop: 420, mobile: 460 },
{ date: "May 31", desktop: 178, mobile: 230 },
{ date: "Jun 4", desktop: 439, mobile: 380 },
{ date: "Jun 8", desktop: 385, mobile: 320 },
{ date: "Jun 12", desktop: 492, mobile: 420 },
{ date: "Jun 16", desktop: 371, mobile: 310 },
{ date: "Jun 20", desktop: 408, mobile: 450 },
{ date: "Jun 24", desktop: 132, mobile: 180 },
{ date: "Jun 28", desktop: 149, mobile: 200 },
];
const monthlyVisitors = [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
{ month: "Mar", desktop: 237, mobile: 120 },
{ month: "Apr", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "Jun", desktop: 214, mobile: 140 },
];
const defaultScales = {
y: { beginAtZero: true },
x: { grid: { display: false } },
};
const initChart = (id, config) => {
const canvas = document.getElementById(id);
if (!canvas || canvas.dataset.chartInitialized) return;
window.basecoat.chart(canvas, config);
};
const initChartExamples = () => {
if (!window.Chart || !window.basecoat?.chart) return;
initChart("chart-example-overview", {
type: "line",
labelKey: "date",
legend: true,
data: visitorsByDay,
series: {
desktop: {
label: "Desktop",
color: "var(--chart-2)",
surface: "gradient",
dataset: {
borderWidth: 1.5,
fill: true,
tension: 0.35,
pointRadius: 0,
},
},
mobile: {
label: "Mobile",
color: "var(--chart-1)",
surface: "gradient",
dataset: {
borderWidth: 1.5,
fill: true,
tension: 0.35,
pointRadius: 0,
},
},
},
options: {
interaction: { mode: "index", intersect: false },
scales: {
x: { grid: { display: false }, ticks: { maxTicksLimit: 6 } },
y: { beginAtZero: true, stacked: true, ticks: { display: false } },
},
},
});
initChart("chart-example-visitors", {
type: "bar",
labelKey: "month",
legend: true,
data: monthlyVisitors,
series: {
desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)" },
},
options: { scales: defaultScales },
});
initChart("chart-example-line", {
type: "line",
labelKey: "month",
data: [
{ month: "Jan", revenue: 42 },
{ month: "Feb", revenue: 68 },
{ month: "Mar", revenue: 61 },
{ month: "Apr", revenue: 90 },
{ month: "May", revenue: 112 },
{ month: "Jun", revenue: 128 },
],
series: {
revenue: {
label: "Revenue",
color: "var(--chart-1)",
dataset: { borderWidth: 1.5, tension: 0, pointRadius: 0, pointHoverRadius: 4 },
},
},
options: { scales: defaultScales },
});
initChart("chart-example-step", {
type: "line",
labelKey: "day",
data: [
{ day: "Mon", queued: 12 },
{ day: "Tue", queued: 18 },
{ day: "Wed", queued: 9 },
{ day: "Thu", queued: 24 },
{ day: "Fri", queued: 16 },
{ day: "Sat", queued: 14 },
],
series: {
queued: {
label: "Queued",
color: "var(--chart-1)",
dataset: { borderWidth: 1.5, stepped: true, pointRadius: 0, pointHoverRadius: 4 },
},
},
options: { scales: defaultScales },
});
initChart("chart-example-stacked", {
type: "bar",
labelKey: "quarter",
legend: true,
data: [
{ quarter: "Q1", won: 32, open: 44, lost: 12 },
{ quarter: "Q2", won: 48, open: 38, lost: 18 },
{ quarter: "Q3", won: 52, open: 46, lost: 16 },
{ quarter: "Q4", won: 61, open: 40, lost: 14 },
],
series: {
won: { label: "Won", color: "var(--chart-1)" },
open: { label: "Open", color: "var(--chart-2)" },
lost: { label: "Lost", color: "var(--chart-3)" },
},
options: {
scales: {
x: { stacked: true, grid: { display: false } },
y: { stacked: true, beginAtZero: true, ticks: { display: false } },
},
},
});
initChart("chart-example-donut", {
type: "doughnut",
labelKey: "source",
legend: true,
data: [
{ source: "Search", visitors: 275, color: "var(--chart-1)" },
{ source: "Direct", visitors: 200, color: "var(--chart-2)" },
{ source: "Social", visitors: 187, color: "var(--chart-3)" },
{ source: "Referral", visitors: 173, color: "var(--chart-4)" },
{ source: "Email", visitors: 90, color: "var(--chart-5)" },
],
series: {
visitors: { label: "Visitors" },
},
options: {
cutout: "62%",
},
});
initChart("chart-example-radar", {
type: "radar",
labelKey: "channel",
legend: true,
data: [
{ channel: "Acquisition", current: 82, previous: 64 },
{ channel: "Activation", current: 72, previous: 70 },
{ channel: "Retention", current: 66, previous: 58 },
{ channel: "Revenue", current: 88, previous: 75 },
{ channel: "Referral", current: 61, previous: 52 },
],
series: {
current: {
label: "Current",
color: "var(--chart-1)",
surface: { from: 0.22 },
dataset: { borderWidth: 1.5, pointRadius: 0, pointHoverRadius: 4 },
},
previous: {
label: "Previous",
color: "var(--chart-2)",
surface: { from: 0.18 },
dataset: { borderWidth: 1.5, pointRadius: 0, pointHoverRadius: 4 },
},
},
options: {
scales: {
r: {
beginAtZero: true,
grid: { color: "color-mix(in oklab, var(--border) 60%, transparent)" },
angleLines: { color: "color-mix(in oklab, var(--border) 60%, transparent)" },
pointLabels: { color: "var(--muted-foreground)" },
ticks: { display: false },
},
},
},
});
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initChartExamples, { once: true });
} else {
initChartExamples();
}
if (!window.__basecoatChartExamplesObserver) {
let pending = false;
const queueInitChartExamples = () => {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
pending = false;
initChartExamples();
});
};
window.__basecoatChartExamplesObserver = new MutationObserver(queueInitChartExamples);
window.__basecoatChartExamplesObserver.observe(document.body, {
childList: true,
subtree: true,
});
}
+106
View File
@@ -0,0 +1,106 @@
import "../../../src/js/basecoat.js";
import "../../../src/js/accordion.js";
const initPriceRange = () => {
const input = document.getElementById("price-range");
const output = document.getElementById("price-range-output");
if (!input || !output || input.dataset.demoRangeInitialized) return;
input.dataset.demoRangeInitialized = "true";
const updateRange = () => {
const min = Number(input.min || 0);
const max = Number(input.max || 100);
const value = Number(input.value || 0);
const percent = max === min ? 0 : ((value - min) / (max - min)) * 100;
input.style.setProperty("--slider-value", `${percent}%`);
output.textContent = `(up to $${value})`;
};
input.addEventListener("input", updateRange);
updateRange();
};
const initCheckboxTables = () => {
document.querySelectorAll("[data-checkbox-table]").forEach((table) => {
if (table.dataset.demoCheckboxTableInitialized) return;
table.dataset.demoCheckboxTableInitialized = "true";
const selectAll = table.querySelector("thead input[type='checkbox']");
const rows = Array.from(table.querySelectorAll("tbody tr"));
const rowCheckboxes = rows
.map((row) => row.querySelector("input[type='checkbox']"))
.filter(Boolean);
const syncRows = () => {
rows.forEach((row) => {
const checkbox = row.querySelector("input[type='checkbox']");
if (checkbox?.checked) {
row.dataset.state = "selected";
} else {
row.removeAttribute("data-state");
}
});
};
const syncSelectAll = () => {
if (!selectAll || rowCheckboxes.length === 0) return;
const checkedCount = rowCheckboxes.filter((checkbox) => checkbox.checked).length;
selectAll.checked = checkedCount === rowCheckboxes.length;
selectAll.indeterminate = checkedCount > 0 && checkedCount < rowCheckboxes.length;
};
const sync = () => {
syncRows();
syncSelectAll();
};
selectAll?.addEventListener("change", () => {
rowCheckboxes.forEach((checkbox) => {
checkbox.checked = selectAll.checked;
});
sync();
});
rowCheckboxes.forEach((checkbox) => {
checkbox.addEventListener("change", sync);
});
sync();
});
};
const initDemos = () => {
initPriceRange();
initCheckboxTables();
};
document.addEventListener("keydown", (event) => {
if (!(event.metaKey || event.ctrlKey) || event.key !== "j") return;
const dialog = document.getElementById("command-basic");
if (!dialog) return;
event.preventDefault();
dialog.open ? dialog.close() : dialog.showModal();
dialog.querySelector("header input")?.focus();
});
document.addEventListener("click", (event) => {
const checkbox = event.target.closest("#dropdown-checkboxes [role='menuitemcheckbox']");
if (checkbox) {
if (checkbox.getAttribute("aria-disabled") === "true") return;
checkbox.setAttribute("aria-checked", checkbox.getAttribute("aria-checked") !== "true");
return;
}
const radio = event.target.closest("#dropdown-radio-group [role='menuitemradio']");
if (radio) {
const group = radio.closest("#dropdown-radio-group");
group?.querySelectorAll("[role='menuitemradio']").forEach((item) => item.setAttribute("aria-checked", "false"));
radio.setAttribute("aria-checked", "true");
}
});
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initDemos, { once: true });
} else {
initDemos();
}
+230
View File
@@ -0,0 +1,230 @@
# Templates
<Callout title="basecoat-cli is deprecated" icon="triangle-alert">
Template files now ship with `basecoat-css`. If you previously copied templates with `basecoat-cli`, install `basecoat-css` instead and copy the files from `node_modules/basecoat-css/templates`.
</Callout>
Basecoat ships optional Nunjucks and Jinja macros for components with larger HTML structures. They generate the same semantic Basecoat markup shown in the component docs, while keeping repeated server-rendered markup easier to maintain.
The templates are versioned with `basecoat-css`, so the generated markup matches the CSS and JavaScript files you installed.
## Macros
| Component | Macro | Nunjucks | Jinja |
| --- | --- | --- | --- |
| [Combobox](/components/combobox) | `combobox()` | [combobox.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/combobox.njk#L18) | [combobox.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/combobox.html.jinja#L4) |
| [Command](/components/command) | `command()` | [command.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk#L12) | [command.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/command.html.jinja#L12) |
| [Command](/components/command) | `command_dialog()` | [command.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk#L79) | [command.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/command.html.jinja#L79) |
| [Dialog](/components/dialog) | `dialog()` | [dialog.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk#L19) | [dialog.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja#L19) |
| [Dropdown Menu](/components/dropdown-menu) | `dropdown_menu()` | [dropdown-menu.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dropdown-menu.njk#L11) | [dropdown-menu.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/dropdown-menu.html.jinja#L11) |
| [Popover](/components/popover) | `popover()` | [popover.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/popover.njk#L10) | [popover.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/popover.html.jinja#L10) |
| [Select](/components/select) | `select()` | [select.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/select.njk#L17) | [select.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/select.html.jinja#L16) |
| [Sidebar](/components/sidebar) | `sidebar()` | [sidebar.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/sidebar.njk#L16) | [sidebar.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/sidebar.html.jinja#L16) |
| [Tabs](/components/tabs) | `tabs()` | [tabs.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/tabs.njk#L16) | [tabs.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/tabs.html.jinja#L17) |
| [Toast](/components/toast) | `toaster()` | [toast.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk#L11) | [toast.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja#L11) |
| [Toast](/components/toast) | `toast()` | [toast.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk#L53) | [toast.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja#L53) |
Some macro files include internal recursive helpers such as `render_select_items()`. Those helpers are implementation details and are not part of the public API.
## Usage
<Steps>
<Step>
### Install Templates
Copy the template folder for your template engine from `basecoat-css`. Templates are meant to be app-owned, so you can edit them after copying.
<CodeGroup>
```bash title="Nunjucks"
cp -R node_modules/basecoat-css/templates/nunjucks ./templates/basecoat
```
```bash title="Jinja"
cp -R node_modules/basecoat-css/templates/jinja ./templates/basecoat
```
</CodeGroup>
Copy only the files you use if you do not want the full template folder.
You can also copy the [Nunjucks templates](https://github.com/hunvreus/basecoat/tree/main/src/nunjucks) or [Jinja templates](https://github.com/hunvreus/basecoat/tree/main/src/jinja) from GitHub.
</Step>
<Step>
### Use a Macro
Import a macro from the copied template file, then call it with the props for that component.
<CodeGroup>
```jinja title="Nunjucks"
{% from "basecoat/select.njk" import select %}
{{ select(
name="fruit",
items=[
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "blueberry", label: "Blueberry" }
]
) }}
```
```jinja title="Jinja"
{% from "basecoat/select.html.jinja" import select %}
{{ select(
name="fruit",
items=[
{ "value": "apple", "label": "Apple" },
{ "value": "banana", "label": "Banana" },
{ "value": "blueberry", "label": "Blueberry" }
]
) }}
```
</CodeGroup>
</Step>
</Steps>
## Macro Reference
### `combobox()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | generated | Unique combobox id. |
| `selected` | `string \| array \| object` | `None` / `none` | Initial selected value or values. Objects can include `value` and `label`. |
| `name` | `string` | `None` / `none` | Hidden input name. |
| `multiple` | `boolean` | `false` | Enables chip-based multiple selection. |
| `placeholder` | `string` | `None` / `none` | Visible input placeholder. |
| `close_on_select` | `boolean` | `false` | Closes the popover after each option selection in multiple mode. |
| `auto_highlight` | `boolean` | `false` | Highlights the first matching option while filtering. |
| `format` | `"value" \| "object"` | `"value"` | Hidden input serialization format. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.combobox` root. |
| `input_attrs` | `object` | `{}` | Extra attributes for the visible input or chip input wrapper. |
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
| `listbox_attrs` | `object` | `{}` | Extra attributes for the listbox. |
| `hidden_input_attrs` | `object` | `{}` | Extra attributes for the hidden input. |
### `command()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | generated | Unique command id. |
| `items` | `array` | `None` / `none` | Optional structured item data. Use caller content when omitted. |
| `placeholder` | `string` | `"Type a command or search..."` | Search input placeholder. |
| `empty_text` | `string` | `"No results found."` | Empty-state text. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.command` root. |
| `input_attrs` | `object` | `{}` | Extra attributes for the search input. |
| `menu_attrs` | `object` | `{}` | Extra attributes for the command menu. |
### `command_dialog()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | generated | Unique command dialog id. |
| `items` | `array` | `None` / `none` | Optional structured item data. Use caller content when omitted. |
| `placeholder` | `string` | `"Type a command or search..."` | Search input placeholder. |
| `empty_text` | `string` | `"No results found."` | Empty-state text. |
| `dialog_attrs` | `object` | `{}` | Extra attributes for the native dialog. |
| `input_attrs` | `object` | `{}` | Extra attributes for the search input. |
| `menu_attrs` | `object` | `{}` | Extra attributes for the command menu. |
| `open` | `boolean` | `false` | Renders the dialog open by default. |
### `dialog()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | generated | Unique dialog id. |
| `trigger` | `string` | `None` / `none` | Text or HTML for a generated trigger button. |
| `title` | `string` | `None` / `none` | Dialog title. |
| `description` | `string` | `None` / `none` | Dialog description. |
| `footer` | `string` | `None` / `none` | HTML for the dialog footer. |
| `dialog_attrs` | `object` | `{}` | Extra attributes for the native dialog. |
| `trigger_attrs` | `object` | `{}` | Extra attributes for the generated trigger button. |
| `header_attrs` | `object` | `{}` | Extra attributes for the dialog header. |
| `body_attrs` | `object` | `{}` | Extra attributes for the dialog body section. |
| `footer_attrs` | `object` | `{}` | Extra attributes for the dialog footer. |
| `open` | `boolean` | `false` | Renders the dialog open by default. |
| `close_button` | `boolean` | `true` | Includes the generated close button. |
| `close_on_overlay_click` | `boolean` | `true` | Adds backdrop-click close behavior. |
### `dropdown_menu()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger` | `string` | required | HTML for the trigger button. |
| `id` | `string` | generated | Unique dropdown menu id. |
| `items` | `array` | `None` / `none` | Optional structured menu item data. Use caller content when omitted. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.dropdown-menu` root. |
| `trigger_attrs` | `object` | `{}` | Extra attributes for the trigger button. |
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
| `menu_attrs` | `object` | `{}` | Extra attributes for the menu element. |
### `popover()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger` | `string` | required | HTML for the trigger button. |
| `id` | `string` | generated | Unique popover id. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.popover` root. |
| `trigger_attrs` | `object` | `{}` | Extra attributes for the trigger button. |
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
### `select()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | generated | Unique select id. |
| `selected` | `string \| array` | `None` / `none` | Initial selected value or values. |
| `name` | `string` | generated from `id` | Hidden input name. |
| `items` | `array` | `None` / `none` | Optional structured item data. Use caller content when omitted. |
| `multiple` | `boolean` | `false` | Enables multiple selection. |
| `placeholder` | `string` | `None` / `none` | Placeholder text for multiple mode with no selection. |
| `close_on_select` | `boolean` | `false` | Closes the popover after each option selection in multiple mode. |
| `format` | `"value" \| "object"` | `"value"` | Hidden input serialization format. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.select` root. |
| `trigger_attrs` | `object` | `{}` | Extra attributes for the trigger button. |
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
| `listbox_attrs` | `object` | `{}` | Extra attributes for the listbox. |
| `input_attrs` | `object` | `{}` | Extra attributes for the hidden input. |
### `sidebar()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | `None` / `none` | Optional sidebar id. Use one when multiple sidebars exist. |
| `label` | `string` | `"Sidebar navigation"` | Navigation landmark label. |
| `open` | `boolean` | `true` | Initial open state. |
| `side` | `"left" \| "right"` | `"left"` | Side of the viewport. |
| `header` | `string` | `None` / `none` | HTML for the sidebar header. |
| `footer` | `string` | `None` / `none` | HTML for the sidebar footer. |
| `menu` | `array` | `None` / `none` | Optional structured menu data. Use caller content when omitted. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.sidebar` root. |
| `header_attrs` | `object` | `{}` | Extra attributes for the header. |
| `content_attrs` | `object` | `{}` | Extra attributes for the scrollable content section. |
| `footer_attrs` | `object` | `{}` | Extra attributes for the footer. |
### `tabs()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | generated | Unique tabs id. |
| `tabsets` | `array` | `[]` | Tab definitions. Each item can include `tab`, `panel`, `tab_attrs`, and `panel_attrs`. |
| `main_attrs` | `object` | `{}` | Extra attributes for the `.tabs` root. |
| `tablist_attrs` | `object` | `{}` | Extra attributes for the tablist. Use `aria-orientation="vertical"` or `data-variant="line"` here. |
| `default_tab_index` | `number` | `1` | One-based index of the initially selected tab. |
### `toaster()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | `"toaster"` | Toaster container id. |
| `toasts` | `array` | `[]` | Initial toast objects rendered inside the toaster. |
| `attrs` | `object` | `{}` | Extra attributes for the `.toaster` container. |
### `toast()`
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `category` | `"success" \| "error" \| "info" \| "warning"` | `"success"` | Toast category. Controls icon and ARIA role. |
| `title` | `string` | `""` | Toast title. |
| `description` | `string` | `""` | Toast description. |
| `action` | `object` | `None` / `none` | Action button/link config. Supports `label`, `onclick`, and `href`. |
| `cancel` | `object` | `None` / `none` | Cancel button/link config. Supports `label`, `onclick`, and `href`. |
| `attrs` | `object` | `{}` | Extra attributes for the `.toast` element. |