Prepare 1.0.0-beta.5

This commit is contained in:
Ronan Berder
2026-06-22 18:19:38 +08:00
parent 9806d6af63
commit cb72d80558
76 changed files with 3361 additions and 1424 deletions
+3
View File
@@ -76,7 +76,10 @@
- Do not include React-only APIs such as `asChild`.
- Do not add "Composition" sections unless they describe stable Basecoat markup.
- Do not add API Reference sections for native/CSS-only components unless there is a real Basecoat API.
- Structure component docs as: title and preview, `## Usage`, optional `### HTML structure` for stable markup requirements, optional `### JavaScript API` for components with Basecoat JS methods/events/options, then `## Examples`.
- Keep `Usage` focused on the minimal working snippet and important Basecoat/upstream differences; put variants, states, RTL, and richer patterns under `Examples`.
- Keep snippets minimal and aligned with current behavior.
- In HTML snippets, keep text-only elements on one line, such as `<p>Text</p>`; use multiline formatting when the element contains child elements or when line length would hurt readability.
- Document intentional differences from upstream near the relevant example.
## Validation
+14
View File
@@ -2,6 +2,20 @@
## Unreleased
## [1.0.0-beta.5] - 2026-06-22
### Added
- Added a dedicated Accordion component with native `<details>` markup, single-item JavaScript behavior, `data-multiple`, disabled item handling, style-pack spacing, and docs.
- Added a dedicated Breadcrumb component with semantic `nav`/`ol` markup, style-pack visuals, collapsed and dropdown examples, and docs.
- Added documented Card action support with `.card-action`.
- Added Combobox clear button, popup trigger, and input-group support, with docs aligned to upstream examples.
### Fixed
- Fixed Combobox single selection reopening immediately after selection and filtering the reopened list to the selected value.
- Improved Combobox multiple chips and remove buttons to match style-pack button sizing more closely.
## [1.0.0-beta.4] - 2026-06-20
### Changed
+2 -37
View File
@@ -1,41 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'astro/config';
import reallySimpleDocs from 'reallysimpledocs/astro';
const rsdCssPath = (relativePath) =>
fileURLToPath(new URL(`./node_modules/reallysimpledocs/src/css/${relativePath}`, import.meta.url));
function basecoatDocsStyles() {
return {
name: 'basecoat-docs-styles',
hooks: {
'astro:config:setup': ({ config }) => {
const root = fileURLToPath(config.root);
const generatedStyles = path.join(root, '.astro', 'reallysimpledocs', 'styles.css');
fs.mkdirSync(path.dirname(generatedStyles), { recursive: true });
fs.writeFileSync(
generatedStyles,
[
'@import "tailwindcss/theme";',
'@import "tailwindcss/utilities";',
'@reference "basecoat-css/base";',
`@import ${JSON.stringify(rsdCssPath('components.css'))};`,
`@import ${JSON.stringify(rsdCssPath('sources.css'))};`,
`@import ${JSON.stringify(rsdCssPath('custom.css'))};`,
`@import ${JSON.stringify(rsdCssPath('overrides.css'))};`,
`@source ${JSON.stringify(fileURLToPath(new URL('./node_modules/reallysimpledocs/src/runtime/**/*.{astro,js,ts}', import.meta.url)))};`,
`@source ${JSON.stringify(path.join(root, 'docs/src/**/*.{md,mdx}'))};`,
'',
].join('\n'),
);
},
},
};
}
export default defineConfig({
site: process.env.SITE_URL || 'https://basecoatui.com',
vite: {
@@ -51,13 +16,14 @@ export default defineConfig({
docsDir: './docs/src',
routeBase: '/',
style: 'vega',
css: false,
components: {
Head: './docs/src/site/StyleHead.astro',
ContentHeader: './docs/src/site/ContentHeader.astro',
},
site: {
title: 'Basecoat',
subtitle: 'v1.0.0-beta.2',
subtitle: 'v1.0.0-beta.4',
description: 'A components library built with Tailwind CSS that works with any web stack.',
url: process.env.SITE_URL || 'https://basecoatui.com',
keywords: ['components', 'component library', 'component system', 'UI', 'UI kit', 'shadcn', 'shadcn/ui', 'Tailwind CSS', 'Tailwind', 'CSS', 'HTML', 'Jinja', 'Nunjucks', 'JS', 'JavaScript', 'vanilla JS', 'vanilla JavaScript'],
@@ -75,6 +41,5 @@ export default defineConfig({
},
},
}),
basecoatDocsStyles(),
],
});
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/luma.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/luma";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/lyra.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/lyra";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/maia.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/maia";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/mira.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/mira";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/nova.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/nova";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/rhea.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/rhea";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/sera.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/sera";
+4 -1
View File
@@ -1,5 +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 "../../../../node_modules/reallysimpledocs/src/css/styles/vega.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/vega";
+305 -57
View File
@@ -1,74 +1,322 @@
# Accordion
<Callout title="No dedicated component" icon="circle-alert">
There is no dedicated Accordion component in Basecoat.
</Callout>
A vertically stacked set of interactive headings that each reveal a section of content.
<Preview class="w-full max-w-md">
<Preview class="w-full max-w-sm">
<section class="accordion">
<details class="group border-b last:border-b-0">
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
Is it accessible?
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 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>
</h2>
</summary>
<section class="pb-4">
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
</section>
</details>
<details class="group border-b last:border-b-0">
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
Is it styled?
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 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>
</h2>
</summary>
<section class="pb-4">
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
</section>
</details>
<details class="group border-b last:border-b-0">
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
Is it animated?
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 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>
</h2>
<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>
<p class="text-sm whitespace-pre-wrap">Yes. It's animated by default, but you can disable it if you prefer.</p>
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>
<script>
(() => {
const accordions = document.querySelectorAll(".accordion");
accordions.forEach((accordion) => {
accordion.addEventListener("click", (event) => {
const summary = event.target.closest("summary");
if (!summary) return;
const details = summary.closest("details");
if (!details) return;
accordion.querySelectorAll("details").forEach((detailsEl) => {
if (detailsEl !== details) {
detailsEl.removeAttribute("open");
}
});
});
});
})();
</script>
</Preview>
## Usage
Basecoat styles native `<details>` disclosure animations. Use semantic `<details>` and `<summary>` markup, then add your own utility classes for the visual treatment.
<Steps>
<Step title="Include the JavaScript files">
```html
<details>
<summary>Is it accessible?</summary>
<p>Yes. It uses native disclosure semantics.</p>
</details>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.4/dist/js/basecoat.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.4/dist/js/accordion.min.js" defer></script>
```
The example above show you how to use a small script to create an accordion with a group of `<details>` elements.
</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>
+118 -30
View File
@@ -1,16 +1,18 @@
# 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">Open alert dialog</button>
<dialog id="demo-alert-dialog" class="dialog" aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
<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="alert-dialog-title">Are you absolutely sure?</h2>
<p id="alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
<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="document.getElementById('demo-alert-dialog').close()">Cancel</button>
<button class="btn" data-variant="primary" onclick="document.getElementById('demo-alert-dialog').close()">Continue</button>
<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>
@@ -18,23 +20,19 @@
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
This component can use the `dialog()` macro for Jinja and Nunjucks.
</Callout>
Alert Dialog uses the same `.dialog` class as [Dialog](/components/dialog), but the example omits the close button and does not close on backdrop click.
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">Open alert dialog</button>
<dialog id="demo-alert-dialog" class="dialog" aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
<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="alert-dialog-title">Are you absolutely sure?</h2>
<p id="alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
<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="document.getElementById('demo-alert-dialog').close()">Cancel</button>
<button class="btn" data-variant="primary" onclick="document.getElementById('demo-alert-dialog').close()">Continue</button>
<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>
@@ -43,32 +41,122 @@ Alert Dialog uses the same `.dialog` class as [Dialog](/components/dialog), but
### HTML structure
<dl>
<dt><code>&lt;button type="button"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The trigger button to open the alert dialog (using <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal" target="_blank">the native <code>showModal()</code> method</a>).</dd>
<dt><code>&lt;dialog class="dialog" id="&#123; DIALOG_ID &#125;"&gt;</code></dt>
<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>
Wraps the content of the alert dialog.
Dialog content surface.
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>
Contains the header for the alert dialog:
Alert dialog header.
<dl>
<dt><code>&lt;h2&gt;</code></dt>
<dd>The title of the alert dialog, must have an <code>id</code> if you use the <code>aria-labelledby</code> attribute on the <code>&lt;dialog&gt;</code>.</dd>
<dt><code>&lt;p&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The description of the alert dialog, must have an <code>id</code> if you use the <code>aria-describedby</code> attribute on the <code>&lt;dialog&gt;</code>.</dd>
<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;section&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>For the content.</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Usually contains actions.</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>
+59 -74
View File
@@ -1,28 +1,16 @@
# Alert
<Preview>
<div class="grid w-full max-w-xl items-start gap-4">
<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>Success! Your changes have been saved</h2>
<section>This is an alert with icon, title and description.</section>
<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-popcorn" xmlns="http://www.w3.org/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 8a2 2 0 0 0 0-4 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0 0 4" /><path d="M10 22 9 8" /><path d="m14 22 1-14" /><path d="M20 8c.5 0 .9.4.8 1l-2.6 12c-.1.5-.7 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L3.2 9c-.1-.6.3-1 .8-1Z" /></svg>
<h2>This Alert has a title and an icon. No description.</h2>
<section>This is an alert with icon, title and description.</section>
</div>
<div class="alert" data-variant="destructive">
<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>Unable to process your payment.</h2>
<section>
<p>Please verify your billing information and try again.</p>
<ul class="list-inside list-disc text-sm">
<li>Check your card details</li>
<li>Ensure sufficient funds</li>
<li>Verify billing address</li>
</ul>
</section>
<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>
@@ -33,9 +21,12 @@ Use `alert` for default styling. Add `data-variant="destructive"` for error stat
```html
<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>Success! Your changes have been saved</h2>
<section>This is an alert with icon, title and description.</section>
<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 using the cli.</section>
<footer>
<button type="button" class="btn" data-variant="outline">Enable</button>
</footer>
</div>
```
@@ -60,63 +51,53 @@ Use `alert` for default styling. Add `data-variant="destructive"` for error stat
## 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
<Preview>
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>Something went wrong!</h2>
<section>Your session has expired. Please log in again.</section>
</div>
</Preview>
### No description
<Preview>
<div class="alert">
<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>
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
</div>
</Preview>
### No icon
<Preview>
<div class="alert">
<h2>Success! Your changes have been saved</h2>
<section>This is an alert with icon, title and description.</section>
<h2>Payment failed</h2>
<section>Your payment could not be processed. Please check your payment method and try again.</section>
</div>
</Preview>
### Action
<Preview>
<div class="grid w-full max-w-xl gap-4">
<div class="alert">
<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>The selected emails have been marked as spam.</h2>
<footer>
<button type="button" class="btn" data-variant="outline" data-size="xs">Undo</button>
</footer>
</div>
<div class="alert">
<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>The selected emails have been marked as spam.</h2>
<section>This is a longer alert description that demonstrates how the action stays aligned to the inline end.</section>
<footer>
<span class="badge">Badge</span>
</footer>
</div>
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
### Custom colors
<Preview>
<div class="alert bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-200">
<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>Storage almost full</h2>
<section class="text-amber-800 dark:text-amber-300">Upgrade your plan or delete unused files.</section>
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>
@@ -124,13 +105,17 @@ Use `alert` for default styling. Add `data-variant="destructive"` for error stat
Add `dir="rtl"` to the alert or an ancestor.
<Preview>
<div class="alert" dir="rtl">
<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>
<footer>
<button type="button" class="btn" data-variant="outline" data-size="xs">عرض</button>
</footer>
<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>
+44 -35
View File
@@ -9,7 +9,6 @@
<span class="avatar rounded-lg">
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<span>ER</span>
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
</span>
<div class="avatar-group [&_.avatar]:grayscale">
<span class="avatar">
@@ -24,14 +23,13 @@
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<span>ER</span>
</span>
<span data-count>+3</span>
</div>
</div>
</Preview>
## Usage
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 `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">
@@ -55,8 +53,8 @@ Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-
<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 data-badge&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Status badge positioned at the block-end inline-end corner.</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>
@@ -71,7 +69,7 @@ Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-
<Preview>
<span class="avatar">
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
</Preview>
@@ -82,36 +80,36 @@ Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-
<span class="avatar">
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
<span class="avatar-badge bg-green-600 dark:bg-green-800"></span>
</span>
</Preview>
### Badge With Icon
### Badge with icon
<Preview>
<span class="avatar grayscale">
<span class="avatar">
<img src="https://github.com/pranathip.png" alt="@pranathip" />
<span>PP</span>
<span data-badge>
<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
### Avatar group
<Preview>
<div class="avatar-group grayscale">
<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
### Avatar group count
<Preview>
<div class="avatar-group grayscale">
<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>
@@ -119,10 +117,10 @@ Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-
</div>
</Preview>
### Avatar Group With Icon
### Avatar group with icon
<Preview>
<div class="avatar-group grayscale">
<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>
@@ -135,19 +133,32 @@ Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-
### Sizes
<Preview>
<div class="flex flex-wrap items-center gap-2 grayscale">
<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 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>
@@ -176,21 +187,19 @@ Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-
### RTL
<Preview>
<div class="flex flex-row flex-wrap items-center gap-6 md:gap-12" dir="rtl">
<div class="flex flex-row flex-wrap items-center gap-12" dir="rtl">
<span class="avatar">
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
<img src="https://github.com/shadcn.png" alt="@shadcn" />
<span>CN</span>
</span>
<span class="avatar">
<span class="avatar rounded-lg">
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
<span>ER</span>
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
</span>
<div class="avatar-group grayscale">
<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>
<span data-count>+٣</span>
</div>
</div>
</Preview>
+69 -55
View File
@@ -1,22 +1,11 @@
# Badge
<Preview>
<div class="flex flex-col items-center gap-2">
<div class="flex w-full flex-wrap gap-2">
<span class="badge">Badge</span>
<span class="badge" data-variant="secondary">Secondary</span>
<span class="badge" data-variant="destructive">Destructive</span>
<span class="badge" data-variant="outline">Outline</span>
</div>
<div class="flex w-full flex-wrap gap-2">
<span class="badge bg-blue-500 text-white dark:bg-blue-600">
<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>
Verified
</span>
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">8</span>
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums" data-variant="destructive">99</span>
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums" data-variant="outline">20+</span>
</div>
<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>
@@ -27,76 +16,101 @@ Add `class="badge"` to an inline element. Add `data-variant` when the badge is n
Inline SVG icons are spaced automatically by the badge gap.
```html
<span class="badge">Badge</span>
<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>
```
Badges support right-to-left layouts through native text flow. Flip directional icons explicitly.
## Examples
### Primary
### Variants
Use `data-variant` to change the badge variant.
<Preview>
<span class="badge">Primary</span>
</Preview>
### Secondary
<Preview>
<span class="badge" data-variant="secondary">Secondary</span>
</Preview>
### Destructive
<Preview>
<span class="badge" data-variant="destructive">Destructive</span>
</Preview>
### Outline
<Preview>
<span class="badge" data-variant="outline">Outline</span>
<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>
<span class="badge" 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>
With icon
</span>
<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" data-variant="outline">
Link
<svg 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>
<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
### Custom colors
<Preview>
<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
Custom
</span>
<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
Flip directional icons explicitly.
<Preview>
<div dir="rtl" class="flex flex-wrap gap-2">
<span class="badge">نشط</span>
<a href="#" class="badge" data-variant="outline">
التفاصيل
<svg 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>
</a>
<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>
+152 -52
View File
@@ -1,62 +1,162 @@
# Breadcrumb
<Callout title="No dedicated component" icon="circle-alert">
There is no dedicated Breadcrumb component in Basecoat.
</Callout>
<Preview>
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
<li class="inline-flex items-center gap-1.5">
<a href="#" class="hover:text-foreground transition-colors">Home</a>
</li>
<li>
<svg class="size-3.5 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 class="inline-flex items-center gap-1.5">
<div id="demo-breadcrumb-menu" class="dropdown-menu">
<button type="button" id="demo-breadcrumb-menu-trigger" aria-haspopup="menu" aria-controls="demo-breadcrumb-menu-menu" aria-expanded="false" class="flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer">
<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="demo-breadcrumb-menu-popover" data-popover aria-hidden="true">
<div role="menu" id="demo-breadcrumb-menu-menu" aria-labelledby="demo-breadcrumb-menu-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>
<svg class="size-3.5 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 class="inline-flex items-center gap-1.5">
<a href="#" class="hover:text-foreground transition-colors">Components</a>
</li>
<li>
<svg class="size-3.5 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 class="inline-flex items-center gap-1.5">
<span class="text-foreground font-normal">Breadcrumb</span>
</li>
</ol>
<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
You can style an ordered list of links combined with chevron icons as show below. The example above demonstrates a more complex use case using the [Dropdown Menu](/components/dropdown-menu) component.
Use a navigation landmark with an ordered list. Mark the current page with `aria-current="page"`.
```html
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
<li class="inline-flex items-center gap-1.5">
<a href="#" class="hover:text-foreground transition-colors">Home</a>
</li>
<li>
<svg class="size-3.5 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 class="inline-flex items-center gap-1.5">
<a href="#" class="hover:text-foreground transition-colors">Components</a>
</li>
</ol>
<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>
```
## 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>
+56 -43
View File
@@ -70,7 +70,7 @@ Use `data-orientation="vertical"` to stack items. Use `<hr role="separator" />`
Use `data-orientation="vertical"` to stack buttons vertically.
<Preview>
<div role="group" class="button-group" data-orientation="vertical">
<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>
@@ -113,6 +113,8 @@ Use `data-orientation="vertical"` to stack buttons vertically.
### 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">
@@ -124,10 +126,10 @@ Use `data-orientation="vertical"` to stack buttons vertically.
</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-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>
<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-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>
<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>
@@ -139,58 +141,60 @@ 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">Copy</button>
<button type="button" class="btn" data-variant="secondary" data-size="sm">Copy</button>
<hr role="separator" />
<button type="button" class="btn" data-variant="secondary">Paste</button>
<button type="button" class="btn" data-variant="secondary" data-size="sm">Paste</button>
</div>
</Preview>
### Split
Use a separator to create a split action with a primary action and a secondary menu trigger.
Use a separator to create a split action.
<Preview>
<div role="group" aria-label="Split save actions" class="button-group">
<button type="button" class="btn" data-variant="secondary">Save</button>
<div role="group" class="button-group">
<button type="button" class="btn" data-variant="secondary">Button</button>
<hr role="separator" />
<div id="dropdown-menu-465061" class="dropdown-menu">
<button type="button" id="dropdown-menu-465061-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-465061-menu" aria-expanded="false" class="btn" data-variant="secondary" data-size="icon" aria-label="More save 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>
</button>
<div id="dropdown-menu-465061-popover" data-popover aria-hidden="true" data-align="end">
<div role="menu" id="dropdown-menu-465061-menu" aria-labelledby="dropdown-menu-465061-trigger">
<div role="menuitem">Save as draft</div>
<div role="menuitem">Save and publish</div>
<div role="menuitem">Save as template</div>
</div>
</div>
</div>
<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 w-full max-w-sm">
<div role="group" aria-label="Search" class="button-group">
<input type="text" class="input" placeholder="Search..." />
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="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
### Input group
<Preview>
<div role="group" aria-label="Amount" class="button-group w-full max-w-sm">
<span>$</span>
<input type="text" class="input" placeholder="0.00" />
<span>USD</span>
<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
### Dropdown menu
<Preview>
<div role="group" class="button-group">
@@ -240,7 +244,7 @@ Use a separator to create a split action with a primary action and a secondary m
### Select
<Preview>
<div class="flex w-fit items-stretch gap-2">
<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">
@@ -282,16 +286,16 @@ Use a separator to create a split action with a primary action and a secondary m
Copilot
</button>
<div id="popover-788465" class="popover">
<button id="popover-788465-trigger" type="button" aria-label="Open copilot tasks" aria-expanded="false" aria-controls="popover-788465-popover" class="btn" data-variant="outline" data-size="icon">
<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 p-0" data-align="end">
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
<section class="p-4 text-sm space-y-2">
<textarea class="textarea w-full mb-4" placeholder="Describe your task in natural language"></textarea>
<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. Copilot will work in the background and open a pull request for your review.</p>
</section>
<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>
@@ -304,12 +308,21 @@ 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">
<button type="button" class="btn" data-variant="outline">أرشفة</button>
<button type="button" class="btn" data-variant="outline">تقرير</button>
<button type="button" class="btn" data-variant="outline">
إرسال
<svg 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>
<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>
+73 -19
View File
@@ -1,7 +1,12 @@
# Button
<Preview>
<button type="button" class="btn">Button</button>
<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
@@ -66,10 +71,22 @@ 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-size="xs">Extra Small</button>
<button type="button" class="btn" data-size="sm">Small</button>
<button type="button" class="btn">Default</button>
<button type="button" class="btn" data-size="lg">Large</button>
<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>
@@ -113,16 +130,16 @@ Use `data-size` to change the size of the button.
<Preview>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Open">
<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>
<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
### 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">
<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>
@@ -130,12 +147,11 @@ Use `data-icon="inline-start"` or `data-icon="inline-end"` on inline icons to ma
### Rounded
Use standard Tailwind classes to override the button radius.
Use the `rounded-full` class to make the button rounded.
<Preview>
<button type="button" class="btn rounded-full">
<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>
Rounded
</button>
</Preview>
@@ -144,31 +160,66 @@ Use standard Tailwind classes to override the button radius.
<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" xmlns="http://www.w3.org/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>
<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="outline">
<button type="button" class="btn" data-variant="secondary" disabled>
Downloading
<svg data-icon="inline-end" 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>
<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
### 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>
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="More options">
<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" 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>
@@ -185,8 +236,11 @@ Add `dir="rtl"` to the button or an ancestor. Flip directional icons explicitly.
إرسال
<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 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>
<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>
+67 -87
View File
@@ -1,30 +1,32 @@
# Card
<Preview class="w-full max-w-sm">
<div class="card w-full">
<div class="card">
<header>
<h2>Login to your account</h2>
<p>Enter your details below to login to your account</p>
<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-form-email">Email</label>
<input class="input" type="email" id="demo-card-form-email" />
<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 gap-2">
<label class="label" for="demo-card-form-password">Password</label>
<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-form-password" />
<input class="input" type="password" id="demo-card-password" required />
</div>
</form>
</section>
<footer class="flex flex-col items-center gap-2">
<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>
<p class="mt-4 text-center text-sm">Don't have an account? <a href="#" class="underline-offset-4 hover:underline">Sign up</a></p>
</footer>
</div>
</Preview>
@@ -36,6 +38,7 @@
<header>
<h2>Card Title</h2>
<p>Card Description</p>
<div class="card-action">Card Action</div>
</header>
<section>
<p>Card Content</p>
@@ -46,7 +49,7 @@
</div>
```
### HTML structure
## HTML structure
<dl>
<dt><code>&lt;div class="card"&gt;</code></dt>
@@ -55,18 +58,20 @@
<dl>
<dt><code>&lt;header&gt;</code></dt>
<dd>
The header of the card.
The card header.
<dl>
<dt><code>&lt;h2&gt;</code></dt>
<dd>The title of the card.</dd>
<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 description of the card.</dd>
<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 content of the card.</dd>
<dd>The main card content.</dd>
<dt><code>&lt;footer&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>The footer of the card.</dd>
<dd>Footer actions or secondary content at the bottom of the card.</dd>
</dl>
</dd>
</dl>
@@ -75,103 +80,78 @@
### Size
<Preview>
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>A compact card with reduced spacing.</p>
<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 class="text-sm">Use <code>data-size="sm"</code> for denser layouts.</p>
<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>
### With content and footer
### Image
<Preview>
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>Meeting Notes</h2>
<p>Transcript from the meeting with the client.</p>
</header>
<section class="text-sm">
<p>Client requested dashboard redesign with focus on mobile responsiveness.</p>
<ol class="mt-4 flex list-decimal flex-col gap-2 ps-6">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</section>
<footer class="flex items-center">
<div class="flex -space-x-2 [&_img]:ring-card [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
<img alt="@hunvreus" src="https://github.com/hunvreus.png" />
<img alt="@shadcn" src="https://github.com/shadcn.png" />
<img alt="@adamwathan" src="https://github.com/adamwathan.png" />
<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>
</footer>
</div>
</Preview>
### With image
<Preview>
<div class="card">
<header>
<h2>Is this an image?</h2>
<p>This is a card with an image.</p>
</header>
<section class="px-0">
<img
alt="Photo by Drew Beamer"
loading="lazy"
width="500"
height="500"
class="aspect-video object-cover" style="color:transparent"
srcset="
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=640&q=75 1x,
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75 2x
"
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75"
/>
</section>
<footer class="flex items-center gap-2">
<span class="badge" data-variant="outline">
<svg class="lucide lucide-bed" xmlns="http://www.w3.org/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 4v16" /><path d="M2 8h18a2 2 0 0 1 2 2v10" /><path d="M2 17h20" /><path d="M6 8v9" /></svg>
1
</span>
<span class="badge" data-variant="outline">
<svg class="lucide lucide-bath" xmlns="http://www.w3.org/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 4 8 6" /><path d="M17 19v2" /><path d="M2 12h20" /><path d="M7 19v2" /><path d="M9 5 7.621 3.621A2.121 2.121 0 0 0 4 5v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5" /></svg>
2
</span>
<span class="badge" data-variant="outline">
<svg class="lucide lucide-land-plot" xmlns="http://www.w3.org/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 8 6-3-6-3v10" /><path d="m8 11.99-5.5 3.14a1 1 0 0 0 0 1.74l8.5 4.86a2 2 0 0 0 2 0l8.5-4.86a1 1 0 0 0 0-1.74L16 12" /><path d="m6.49 12.85 11.02 6.3" /><path d="M17.51 12.85 6.5 19.15" /></svg>
350m²
</span>
<span class="ms-auto font-medium tabular-nums">
$135,000
</span>
<footer>
<button type="button" class="btn w-full">View Event</button>
</footer>
</div>
</Preview>
### RTL
## RTL
Add `dir="rtl"` to the card or an ancestor.
<Preview>
<Preview class="w-full max-w-sm">
<div class="card" dir="rtl">
<header>
<h2>إعدادات الحساب</h2>
<p>راجع إعدادات الأمان والإشعارات.</p>
<h2>تسجيل الدخول إلى حسابك</h2>
<p>أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك</p>
<div class="card-action">
<button type="button" class="btn" data-variant="link">إنشاء حساب</button>
</div>
</header>
<section>
<p class="text-sm">يمكنك تعديل هذه الإعدادات في أي وقت.</p>
<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>
<button type="button" class="btn">حفظ</button>
<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>
+6 -6
View File
@@ -149,37 +149,37 @@ window.basecoat.chart("#custom-chart", {
## Examples
### Bar Chart - Multiple
### Bar chart - multiple
<Preview class="w-full">
<canvas id="chart-example-visitors" aria-label="Monthly visitors by device"></canvas>
</Preview>
### Line Chart - Linear
### Line chart - linear
<Preview class="w-full">
<canvas id="chart-example-line" aria-label="Revenue line chart"></canvas>
</Preview>
### Line Chart - Step
### Line chart - step
<Preview class="w-full">
<canvas id="chart-example-step" aria-label="Support queue step chart"></canvas>
</Preview>
### Bar Chart - Stacked
### Bar chart - stacked
<Preview class="w-full">
<canvas id="chart-example-stacked" aria-label="Stacked pipeline bar chart"></canvas>
</Preview>
### Pie Chart - Donut
### Pie chart - donut
<Preview class="w-full">
<canvas id="chart-example-donut" aria-label="Traffic source donut chart"></canvas>
</Preview>
### Radar Chart
### Radar chart
<Preview class="w-full">
<canvas id="chart-example-radar" aria-label="Channel performance radar chart"></canvas>
+127 -53
View File
@@ -1,9 +1,31 @@
# Checkbox
<Preview>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="terms" class="input" />
<label for="terms">Accept terms and conditions</label>
<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>
@@ -19,65 +41,83 @@ Add `class="input"` to checkbox inputs. Pair it with [Field](/components/field)
### Basic
Pair the checkbox with `field` and a native `<label>` for proper layout and labeling.
<Preview>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="checkbox-basic" class="input" />
<label for="checkbox-basic">Accept terms and conditions</label>
<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
<Preview>
<div role="group" class="field" data-orientation="horizontal">
<input type="checkbox" id="checkbox-description" class="input" aria-describedby="checkbox-description-text" />
<section>
<label for="checkbox-description">Accept terms and conditions</label>
<p id="checkbox-description-text">By clicking this checkbox, you agree to the terms and conditions.</p>
</section>
</div>
</Preview>
### Invalid
Use a `section` and paragraph for helper text.
<Preview>
<div role="group" class="field" data-orientation="horizontal" data-invalid="true">
<input type="checkbox" id="checkbox-invalid" class="input" aria-invalid="true" />
<label for="checkbox-invalid">Accept terms and conditions</label>
<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="field" data-orientation="horizontal" data-disabled="true">
<input type="checkbox" id="checkbox-disabled" class="input" disabled />
<label for="checkbox-disabled">Accept terms and conditions</label>
<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>Sidebar</legend>
<p>Select the items you want to display in the sidebar.</p>
<div role="group" aria-label="Sidebar items" data-slot="checkbox-group">
<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="checkbox-recents" checked />
<label for="checkbox-recents">Recents</label>
<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="checkbox-home" checked />
<label for="checkbox-home">Home</label>
<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="checkbox-applications" />
<label for="checkbox-applications">Applications</label>
<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="checkbox-desktop" />
<label for="checkbox-desktop">Desktop</label>
<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>
@@ -85,42 +125,76 @@ Add `class="input"` to checkbox inputs. Pair it with [Field](/components/field)
### Table
<Preview>
<Preview class="w-full">
<div class="table-container">
<table class="table">
<table class="table" data-checkbox-table>
<thead>
<tr>
<th><input type="checkbox" class="input" aria-label="Select all" /></th>
<th>Status</th>
<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>
<td><input type="checkbox" class="input" aria-label="Select row 1" checked /></td>
<td>Active</td>
<td>olivia@example.com</td>
<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" class="input" aria-label="Select row 2" /></td>
<td>Inactive</td>
<td>noah@example.com</td>
<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
## RTL
Checkbox layout uses document direction and logical spacing. Set `dir="rtl"` on a parent to render right-to-left.
Add `dir="rtl"` to the field group or an ancestor.
<Preview>
<div dir="rtl" class="field" data-orientation="horizontal">
<input type="checkbox" id="checkbox-rtl" class="input" aria-describedby="checkbox-rtl-description" checked />
<section>
<label for="checkbox-rtl">قبول الشروط والأحكام</label>
<p id="checkbox-rtl-description">يمكنك تغيير هذا الخيار في أي وقت.</p>
</section>
<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>
+148 -103
View File
@@ -1,11 +1,11 @@
# Combobox
<Preview>
<div id="framework-combobox" class="combobox w-[240px]">
<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 framework found.">
<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>
@@ -41,7 +41,7 @@ Combobox is input-first. The visible input filters the list; the hidden input st
<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 framework found.">
<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>
@@ -61,40 +61,24 @@ Combobox is input-first. The visible input filters the list; the hidden input st
<dl>
<dt><code>&lt;div class="combobox"&gt;</code></dt>
<dd>
Root element. Add <code>data-auto-highlight="true"</code> to highlight the first match while filtering. Add <code>data-format="object"</code> to serialize selected values as <code>&#123; value, label &#125;</code> objects in the hidden input. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote autocomplete or a local Lunr index.
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 should include <code>aria-expanded</code>, <code>aria-controls</code>, and <code>aria-autocomplete="list"</code>.</dd>
<dt><code>&lt;div class="combobox-chips"&gt;</code> <span class="badge" data-variant="secondary">Multiple only</span></dt>
<dd>
Wraps selected chips and the editable input in multiple mode.
<dl>
<dt><code>&lt;input type="text" role="combobox"&gt;</code></dt>
<dd>The editable control in multiple mode.</dd>
</dl>
</dd>
<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.
<dl>
<dt><code>&lt;div role="listbox"&gt;</code></dt>
<dd>
The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode and <code>data-empty</code> for the empty message.
<dl>
<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 visible input should use different text than the rendered content.</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;div role="heading"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>Group heading.</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>
<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>, single mode stores a JSON object and multiple mode stores a JSON array of objects.</dd>
<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>
@@ -104,20 +88,19 @@ Combobox is input-first. The visible input filters the list; the hidden input st
| 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, useful for remote autocomplete hydration. |
| `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>
<div id="frameworks-combobox" class="combobox" data-auto-highlight="true">
<div class="combobox-chips w-[320px]">
<input type="text" role="combobox" placeholder="Add framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="frameworks-combobox-listbox" />
</div>
<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 framework found.">
<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>
@@ -129,26 +112,66 @@ Combobox is input-first. The visible input filters the list; the hidden input st
</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 w-[280px]">
<input type="text" role="combobox" placeholder="Search timezone" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="timezone-combobox-listbox" />
<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" aria-orientation="vertical" data-empty="No timezone found.">
<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="America/New_York">(GMT-5) New York</div>
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</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="Europe/London">(GMT+0) London</div>
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</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>
@@ -156,32 +179,17 @@ Combobox is input-first. The visible input filters the list; the hidden input st
</div>
</Preview>
### Custom Items
### Custom items
<Preview>
<div id="country-combobox" class="combobox w-[280px]">
<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 country 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 (ar)</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 (jp)</span>
</span>
</div>
<div role="option" data-value="united-states" data-label="United States" data-filter="United States North America USA">
<span class="flex flex-col">
<span>United States</span>
<span class="text-muted-foreground text-xs">North America (us)</span>
</span>
</div>
<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="" />
@@ -193,11 +201,11 @@ Combobox is input-first. The visible input filters the list; the hidden input st
<Preview>
<div role="group" class="field" data-invalid="true">
<label for="invalid-combobox-input">Framework</label>
<div id="invalid-combobox" class="combobox w-[240px]">
<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 framework found.">
<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>
@@ -214,26 +222,28 @@ Combobox is input-first. The visible input filters the list; the hidden input st
### Disabled
<Preview>
<div id="combobox-489412" 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="combobox-489412-listbox" disabled="disabled" />
<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="combobox-489412-popover" data-popover aria-hidden="true">
<div role="listbox" id="combobox-489412-listbox" aria-orientation="vertical">
<div role="option" data-value="nextjs">Next.js</div>
<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
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="combobox-906805" class="combobox w-[240px]" 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="combobox-906805-listbox" />
<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="combobox-906805-popover" data-popover aria-hidden="true">
<div role="listbox" id="combobox-906805-listbox" aria-orientation="vertical">
<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>
@@ -245,19 +255,53 @@ Combobox is input-first. The visible input filters the list; the hidden input st
</div>
</Preview>
### Top Side
### Popup
<Preview>
<div id="combobox-775269" 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="combobox-775269-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="combobox-775269-popover" data-popover aria-hidden="true" data-side="top">
<div role="listbox" id="combobox-775269-listbox" aria-orientation="vertical">
<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 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="" />
@@ -267,18 +311,19 @@ Combobox is input-first. The visible input filters the list; the hidden input st
### RTL
<Preview>
<div dir="rtl">
<div id="combobox-998046" class="combobox w-[240px]">
<input type="text" role="combobox" placeholder="اختر إطار عمل" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-998046-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="combobox-998046-popover" data-popover aria-hidden="true">
<div role="listbox" id="combobox-998046-listbox" aria-orientation="vertical">
<div role="option" data-value="nextjs">Next.js</div>
<div role="option" data-value="sveltekit">SvelteKit</div>
<div role="option" data-value="astro">Astro</div>
<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="" />
<input type="hidden" value='["technology"]' />
</div>
</div>
</Preview>
+149 -202
View File
@@ -1,12 +1,12 @@
# Command
<Preview class="w-full max-w-sm">
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
<div id="demo-command-standalone" class="command rounded-lg 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." class="scrollbar-sm">
<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">
@@ -28,24 +28,23 @@
<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>
## About
shadcn/ui's Command component is built on `cmdk`. Basecoat implements the same command-menu pattern with vanilla HTML, CSS, and a small JavaScript controller for filtering, active item navigation, and dialog closing.
## Usage
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#command" }}>
@@ -69,7 +68,7 @@ shadcn/ui's Command component is built on `cmdk`. Basecoat implements the same c
<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." class="scrollbar-sm">
<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">
@@ -91,14 +90,17 @@ shadcn/ui's Command component is built on `cmdk`. Basecoat implements the same c
<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>
@@ -126,7 +128,7 @@ The command menu can be used standalone or inside a native dialog. The structure
<dl>
<dt><code>&lt;dialog class="command-dialog"&gt;</code> <span class="badge" data-variant="secondary">Optional</span></dt>
<dd>
Dialog variant. Wraps one <code>&lt;div class="command"&gt;</code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
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>
@@ -149,7 +151,7 @@ The command menu can be used standalone or inside a native dialog. The structure
<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>, and <code>aria-disabled="true"</code>.</dd>
<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>
@@ -174,22 +176,75 @@ The command menu can be used standalone or inside a native dialog. The structure
### Basic
A simple command menu in a dialog. The button also shows the keyboard shortcut used in this example.
A simple command menu in a dialog.
<Preview>
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-basic').showModal()">
Open command menu
<kbd class="kbd">⌘J</kbd>
</button>
<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." class="scrollbar-sm">
<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>
@@ -198,235 +253,105 @@ A simple command menu in a dialog. The button also shows the keyboard shortcut u
<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">
<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-basic-settings">
<span role="heading" id="command-basic-settings">Settings</span>
<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>
<script>
document.addEventListener("keydown", (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === "j") {
event.preventDefault();
const dialog = document.getElementById("command-basic");
dialog.open ? dialog.close() : dialog.showModal();
dialog.querySelector("header input")?.focus();
}
});
</script>
</Preview>
### Shortcuts
<Preview class="w-full max-w-sm">
<div id="command-shortcuts" 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-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." class="scrollbar-sm">
<div role="group" aria-labelledby="command-shortcuts-suggestions">
<span role="heading" id="command-shortcuts-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>
<span data-shortcut>⌘K</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>
<span data-shortcut>⌘E</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="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>
</Preview>
### Groups
<Preview class="w-full max-w-sm">
<div id="command-groups" 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-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." class="scrollbar-sm">
<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" 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="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>
</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>
</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>
</div>
</div>
</div>
</div>
</Preview>
### Scrollable
<Preview class="w-full max-w-sm">
<div id="command-scrollable" 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-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." class="scrollbar-sm">
<div role="group" aria-labelledby="command-scrollable-heading">
<span role="heading" id="command-scrollable-heading">Pages</span>
<div role="menuitem" data-filter="Page 1">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 1</span>
<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>
<div role="menuitem" data-filter="Page 2">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 2</span>
<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>
<div role="menuitem" data-filter="Page 3">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 3</span>
<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>
<div role="menuitem" data-filter="Page 4">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 4</span>
<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>
<div role="menuitem" data-filter="Page 5">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 5</span>
</div>
<div role="menuitem" data-filter="Page 6">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 6</span>
</div>
<div role="menuitem" data-filter="Page 7">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 7</span>
</div>
<div role="menuitem" data-filter="Page 8">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 8</span>
</div>
<div role="menuitem" data-filter="Page 9">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 9</span>
</div>
<div role="menuitem" data-filter="Page 10">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 10</span>
</div>
<div role="menuitem" data-filter="Page 11">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 11</span>
</div>
<div role="menuitem" data-filter="Page 12">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 12</span>
</div>
<div role="menuitem" data-filter="Page 13">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 13</span>
</div>
<div role="menuitem" data-filter="Page 14">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 14</span>
</div>
<div role="menuitem" data-filter="Page 15">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 15</span>
</div>
<div role="menuitem" data-filter="Page 16">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 16</span>
</div>
<div role="menuitem" data-filter="Page 17">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 17</span>
</div>
<div role="menuitem" data-filter="Page 18">
<svg class="lucide lucide-file" xmlns="http://www.w3.org/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" /></svg>
<span>Page 18</span>
<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>
</div>
</dialog>
</Preview>
### RTL
## RTL
Command supports document direction. Set `dir="rtl"` on the command root or a parent element.
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" />
<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="No results found." class="scrollbar-sm">
<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">
@@ -434,6 +359,28 @@ Command supports document direction. Set `dir="rtl"` on the command root or a pa
<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>
+15 -11
View File
@@ -77,7 +77,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
<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. The macro also adds backdrop-click close handling.
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>
@@ -107,7 +107,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
## Examples
### Custom Close Button
### Custom close button
<Preview>
<button type="button" onclick="document.getElementById('dialog-custom-close').showModal()" class="btn" data-variant="outline">Share</button>
@@ -120,7 +120,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
<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://ui.shadcn.com/docs/installation" readonly />
<input class="input" id="dialog-custom-close-link" value="https://basecoatui.com/installation/" readonly />
</div>
</section>
<footer class="sm:justify-start">
@@ -130,7 +130,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
</dialog>
</Preview>
### No Close Button
### No close button
<Preview>
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn" data-variant="outline">No Close Button</button>
@@ -144,7 +144,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
</dialog>
</Preview>
### Sticky Footer
### Sticky footer
<Preview>
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn" data-variant="outline">Sticky Footer</button>
@@ -174,7 +174,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
</dialog>
</Preview>
### Scrollable Content
### Scrollable content
<Preview>
<button type="button" onclick="document.getElementById('dialog-scrollable').showModal()" class="btn" data-variant="outline">Scrollable Content</button>
@@ -209,24 +209,28 @@ Dialog positioning and close button placement use logical properties. Set `dir="
<Preview>
<div dir="rtl">
<button type="button" onclick="document.getElementById('dialog-rtl').showModal()" class="btn" data-variant="outline">فتح</button>
<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>
<div class="sm:max-w-sm">
<header>
<h2 id="dialog-rtl-title">تعديل الملف الشخصي</h2>
<p id="dialog-rtl-description">قم بتحديث بياناتك ثم احفظ التغييرات.</p>
<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" value="ليلى" />
<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>
<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>
+262 -82
View File
@@ -3,7 +3,7 @@
<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="min-w-56">
<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>
@@ -15,15 +15,32 @@
<span>Billing</span>
<kbd>⌘B</kbd>
</div>
<div role="menuitem">Settings</div>
<div role="menuitem">Keyboard shortcuts</div>
<div role="menuitem">
<span>Settings</span>
<kbd>⌘S</kbd>
</div>
</div>
<hr role="separator" />
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
<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="menuitem">Log out</div>
<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>
@@ -48,7 +65,7 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
```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="min-w-56">
<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>
@@ -60,15 +77,32 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
<span>Billing</span>
<kbd>⌘B</kbd>
</div>
<div role="menuitem">Settings</div>
<div role="menuitem">Keyboard shortcuts</div>
<div role="menuitem">
<span>Settings</span>
<kbd>⌘S</kbd>
</div>
</div>
<hr role="separator" />
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
<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="menuitem">Log out</div>
<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>
@@ -141,7 +175,8 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
<div role="menuitem">Settings</div>
</div>
<hr role="separator" />
<div role="menuitem">Team</div>
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
</div>
</div>
@@ -155,22 +190,25 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
<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="menuitem">
<span>New Tab</span>
<kbd>⌘T</kbd>
</div>
<div role="menuitem">
<span>New Window</span>
<kbd>⌘N</kbd>
</div>
<div role="menuitem">
<span>New Private Window</span>
<kbd>⇧⌘N</kbd>
<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>Print</span>
<kbd>⌘P</kbd>
<span>Log out</span>
<kbd>⇧⌘Q</kbd>
</div>
</div>
</div>
@@ -211,7 +249,7 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
<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="min-w-32">
<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>
@@ -219,42 +257,63 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
<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>
<span>Status Bar</span>
<kbd>⌘S</kbd>
</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>
<span>Activity Bar</span>
<kbd>⌘A</kbd>
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>
<span>Panel</span>
<kbd>⌘P</kbd>
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>
<script>
(() => {
const dropdownMenu = document.querySelector("#dropdown-checkboxes");
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
checkboxes.forEach((checkbox) => {
checkbox.addEventListener("click", () => {
if (checkbox.getAttribute("aria-disabled") === "true") return;
checkbox.setAttribute("aria-checked", checkbox.getAttribute("aria-checked") !== "true");
});
});
})();
</script>
</Preview>
### Radio Group
### 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">
@@ -285,33 +344,103 @@ Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` w
</div>
</div>
</div>
<script>
(() => {
const dropdownMenu = document.querySelector("#dropdown-radio-group");
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
radios.forEach((radio) => {
radio.addEventListener("click", () => {
radios.forEach((item) => item.setAttribute("aria-checked", "false"));
radio.setAttribute("aria-checked", "true");
});
});
})();
</script>
</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">Open</button>
<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="menuitem">Edit</div>
<div role="menuitem">Duplicate</div>
<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="menuitem" data-variant="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
<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>
@@ -325,20 +454,71 @@ Dropdown menus support document direction. Set `dir="rtl"` on the dropdown root
<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="min-w-32" data-align="end">
<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="heading" id="dropdown-rtl-label">الحساب</div>
<div role="menuitem">
<span>الملف الشخصي</span>
<kbd>⌘P</kbd>
</div>
<div role="menuitem">
<span>الإعدادات</span>
<kbd>⌘S</kbd>
<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="menuitem" data-variant="destructive">حذف</div>
<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>
+43 -14
View File
@@ -14,7 +14,7 @@
<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">
<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>
@@ -40,7 +40,7 @@ Add `class="empty"` to a semantic container. The component styles common childre
<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">
<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>
@@ -77,8 +77,8 @@ Add `class="empty"` to a semantic container. The component styles common childre
### Outline
<Preview>
<section class="empty border">
<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>
@@ -92,16 +92,36 @@ Add `class="empty"` to a semantic container. The component styles common childre
</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 alt="@shadcn" src="https://github.com/shadcn.png" />
<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 or try again later.</p>
<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>
@@ -109,15 +129,15 @@ Add `class="empty"` to a semantic container. The component styles common childre
</section>
</Preview>
### Avatar Group
### 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="@hunvreus" src="https://github.com/hunvreus.png" />
<img alt="@shadcn" src="https://github.com/shadcn.png" />
<img alt="@adamwathan" src="https://github.com/adamwathan.png" />
<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>
@@ -131,7 +151,7 @@ Add `class="empty"` to a semantic container. The component styles common childre
</section>
</Preview>
### Input Group
### Input group
<Preview>
<section class="empty">
@@ -141,9 +161,11 @@ Add `class="empty"` to a semantic container. The component styles common childre
</header>
<footer>
<div class="input-group sm:w-3/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>
<input type="search" placeholder="Try searching for pages..." />
<kbd class="kbd">/</kbd>
<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>
@@ -159,10 +181,17 @@ Add `class="empty"` to a semantic container. The component styles common childre
<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>
<p>لم تقم بإنشاء أي مشاريع بعد. ابدأ بإنشاء مشروعك الأول.</p>
</header>
<footer>
<button class="btn" data-size="sm">إنشاء مشروع</button>
<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>
+5 -22
View File
@@ -1,7 +1,7 @@
# Field
<Preview>
<form class="w-full max-w-md">
<Preview class="w-full max-w-md">
<form>
<div role="group" aria-label="Checkout fields" class="fieldset">
<fieldset class="fieldset">
<legend>Payment Method</legend>
@@ -186,23 +186,6 @@ Use `class="field"` for one control and `class="fieldset"` for related controls.
<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>
<script>
(() => {
const input = document.getElementById("price-range");
const output = document.getElementById("price-range-output");
if (!input || !output) return;
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();
})();
</script>
</Preview>
### Fieldset
@@ -304,7 +287,7 @@ Use `class="field"` for one control and `class="fieldset"` for related controls.
</div>
</Preview>
### Choice Card
### Choice card
Wrap a `.field` inside a `<label>` to create selectable field cards. This works with radio, checkbox, and switch controls.
@@ -335,7 +318,7 @@ Wrap a `.field` inside a `<label>` to create selectable field cards. This works
</fieldset>
</Preview>
### Field Group
### Field group
Stack fields inside a group and use `.field-separator` to divide sections.
@@ -371,7 +354,7 @@ Stack fields inside a group and use `.field-separator` to divide sections.
</div>
</Preview>
### Responsive Layout
### Responsive layout
Use `data-orientation="responsive"` to stack by default and switch to horizontal in wider containers.
+5 -5
View File
@@ -49,7 +49,7 @@ Add `class="input-group"` to the root. Native controls stay semantic and become
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
### Inline start
<Preview class="w-full max-w-sm">
<div class="field max-w-sm">
@@ -64,7 +64,7 @@ Use `data-align="start|end"` on addon wrappers to position them inline. Use `dat
</div>
</Preview>
### inline-end
### Inline end
<Preview class="w-full max-w-sm">
<div class="field max-w-sm">
@@ -79,7 +79,7 @@ Use `data-align="start|end"` on addon wrappers to position them inline. Use `dat
</div>
</Preview>
### block-start
### Block start
<Preview class="w-full max-w-sm">
<div role="group" aria-label="Block start input group examples" class="fieldset max-w-sm">
@@ -110,7 +110,7 @@ Use `data-align="start|end"` on addon wrappers to position them inline. Use `dat
</div>
</Preview>
### block-end
### Block end
<Preview class="w-full max-w-sm">
<div role="group" aria-label="Block end input group examples" class="fieldset max-w-sm">
@@ -338,7 +338,7 @@ Use `data-align="start|end"` on addon wrappers to position them inline. Use `dat
</div>
</Preview>
### Custom Input
### Custom input
<Preview class="w-full max-w-sm">
<div class="input-group" data-orientation="vertical">
+15 -15
View File
@@ -27,17 +27,17 @@ Use native HTML attributes for behavior: `disabled` for disabled inputs, `requir
Use `field` with a label and description to create an accessible field.
<Preview class="w-full max-w-xs">
<div role="group" class="field 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
### Field group
<Preview class="w-full max-w-xs">
<div class="grid w-full max-w-xs gap-4">
<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" />
@@ -58,7 +58,7 @@ Use `field` with a label and description to create an accessible field.
### Invalid
<Preview class="w-full max-w-xs">
<div role="group" class="field w-full max-w-xs">
<div role="group" class="field">
<label for="input-invalid">Email</label>
<input class="input" id="input-invalid" type="email" placeholder="Email" aria-invalid="true" />
<p>Please enter a valid email address.</p>
@@ -74,7 +74,7 @@ Use `field` with a label and description to create an accessible field.
### Inline
<Preview class="w-full max-w-xs">
<div class="flex w-full max-w-xs items-center gap-2">
<div class="flex items-center gap-2">
<input class="input" type="search" placeholder="Search..." />
<button type="submit" class="btn">Search</button>
</div>
@@ -82,8 +82,8 @@ Use `field` with a label and description to create an accessible field.
### Grid
<Preview class="w-full max-w-xl">
<div class="grid w-full gap-4 md:grid-cols-2">
<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" />
@@ -93,7 +93,7 @@ Use `field` with a label and description to create an accessible field.
### Required
<Preview class="w-full max-w-xs">
<div role="group" class="field 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>
@@ -102,7 +102,7 @@ Use `field` with a label and description to create an accessible field.
### Badge
<Preview class="w-full max-w-xs">
<div role="group" class="field 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>
@@ -111,12 +111,12 @@ Use `field` with a label and description to create an accessible field.
</div>
</Preview>
### Input Group
### 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 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>
@@ -124,12 +124,12 @@ Use the [Input Group](/components/input-group/) patterns to add icons, text, or
</div>
</Preview>
### Button Group
### 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 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>
@@ -140,7 +140,7 @@ Use [Button Group](/components/button-group/) when an input should be grouped wi
### Form
<Preview class="w-full max-w-sm">
<form class="w-full max-w-sm space-y-6">
<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 />
@@ -159,7 +159,7 @@ Use [Button Group](/components/button-group/) when an input should be grouped wi
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 w-full max-w-xs" dir="rtl">
<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>
+1 -1
View File
@@ -56,7 +56,7 @@ Keyboard labels are direction-neutral. Surrounding spacing should use logical ut
</div>
</Preview>
### Input Group
### Input group
<Preview>
<div class="relative w-full max-w-sm">
+1 -1
View File
@@ -174,7 +174,7 @@ Basecoat popovers are inline-positioned relative to the `.popover` wrapper. This
</div>
</Preview>
### With Form
### With form
<Preview>
<div id="popover-form" class="popover">
+8 -8
View File
@@ -1,7 +1,7 @@
# Progress
<Preview class="w-full max-w-sm">
<div class="progress" role="progressbar" aria-label="Loading" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<Preview>
<div class="progress max-w-sm" role="progressbar" aria-label="Loading" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<span style="width: 66%"></span>
</div>
</Preview>
@@ -29,11 +29,11 @@ Add `class="progress"` and `role="progressbar"` to the root. Use one child `<spa
### Label
<Preview class="w-full max-w-sm">
<div class="field">
<div class="flex items-center gap-3">
<Preview>
<div class="field max-w-sm">
<div class="flex items-center gap-3 text-sm">
<label id="progress-label">Upload progress</label>
<output class="text-muted-foreground ms-auto text-sm tabular-nums">66%</output>
<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>
@@ -45,8 +45,8 @@ Add `class="progress"` and `role="progressbar"` to the root. Use one child `<spa
Progress uses logical direction. Set `dir="rtl"` on the progress root or a parent to fill from the right.
<Preview class="w-full max-w-sm">
<div dir="rtl" class="progress" role="progressbar" aria-label="تحميل" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<Preview>
<div dir="rtl" class="progress max-w-sm" role="progressbar" aria-label="تحميل" aria-valuenow="66" aria-valuemin="0" aria-valuemax="100">
<span style="width: 66%"></span>
</div>
</Preview>
+1 -1
View File
@@ -66,7 +66,7 @@ Add `class="input"` to radio inputs. Use a shared `name` attribute for mutually
</div>
</Preview>
### Choice Card
### Choice card
Wrap a `.field` in a native `<label>` when the whole card should select the option.
+1 -1
View File
@@ -79,7 +79,7 @@ Add a spinner to a badge to indicate a loading state. Place the spinner before o
</div>
</Preview>
### Input Group
### Input group
<Preview class="w-full max-w-md">
<div class="flex w-full max-w-md flex-col gap-4">
+1 -1
View File
@@ -38,7 +38,7 @@ Add `class="input"` and `role="switch"` to a native checkbox. Use a native `<lab
</div>
</Preview>
### Choice Card
### Choice card
Use a wrapping `<label>` when the whole card should toggle the switch.
+2 -2
View File
@@ -37,7 +37,7 @@ Basecoat tooltips are CSS-only and text-only. This intentionally differs from sh
</div>
</Preview>
### Icon Button
### Icon button
<Preview>
<button class="btn" data-variant="ghost" data-size="icon" data-tooltip="Additional information" aria-label="Info">
@@ -45,7 +45,7 @@ Basecoat tooltips are CSS-only and text-only. This intentionally differs from sh
</button>
</Preview>
### Disabled Button
### Disabled button
<Preview>
<span class="inline-block" data-tooltip="This feature is currently unavailable">
+4
View File
@@ -52,6 +52,10 @@ Or load the Basecoat runtime once, then only the component scripts you use.
</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 |
+3
View File
@@ -7,6 +7,7 @@ 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";
const styleUrls = {
vega: vegaUrl,
@@ -78,3 +79,5 @@ const styleUrls = {
if (!existing) document.head.appendChild(link);
})();
</script>
<script type="module" src={demoInteractionsUrl}></script>
+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();
}
+6 -6
View File
@@ -1,12 +1,12 @@
{
"name": "basecoat",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "basecoat",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"license": "MIT",
"workspaces": [
"packages/*"
@@ -41,12 +41,12 @@
},
"devDependencies": {
"astro": "^6.4.6",
"basecoat-css": "1.0.0-beta.3",
"basecoat-css": "^1.0.0-beta.4",
"tailwindcss": "4.1.17"
},
"peerDependencies": {
"astro": "^6.4.6",
"basecoat-css": "^1.0.0-beta.3",
"basecoat-css": "^1.0.0-beta.4",
"tailwindcss": ">=4.1.17 <5"
}
},
@@ -8951,7 +8951,7 @@
},
"packages/cli": {
"name": "basecoat-cli",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"license": "MIT",
"dependencies": {
"commander": "^13.1.0",
@@ -8964,7 +8964,7 @@
},
"packages/css": {
"name": "basecoat-css",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"license": "MIT"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "basecoat",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"description": "shadcn/ui for the rest of us",
"author": "hunvreus",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "basecoat-cli",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"description": "Add Basecoat components to your project",
"author": "hunvreus",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "basecoat-css",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"description": "Tailwind CSS for Basecoat components",
"author": {
"name": "hunvreus",
+227 -111
View File
@@ -1,161 +1,277 @@
# RSD Astro Docs Authoring Handoff
# Basecoat docs migration to latest ReallySimpleDocs
## Purpose
Capture the current thinking for showing rendered Basecoat examples plus source code when ReallySimpleDocs is used as an Astro integration.
Migrate the Basecoat docs site to the latest local ReallySimpleDocs integration in `../reallysimpledocs`.
The next session should decide whether Basecoat keeps its current Nunjucks-generated RSD docs flow, or whether RSD should grow an Astro-native optional examples API for richer docs pages.
This plan is for the next agent. It is based on the current Basecoat repo at `/Users/hunvreus/Workspace/basecoat` and the current RSD repo at `/Users/hunvreus/Workspace/reallysimpledocs`.
## Current state
- ReallySimpleDocs now ships as an Astro integration package.
- RSD content is currently plain Markdown rendered through `marked`.
- RSD docs pages are generated by:
- `/Users/hunvreus/Workspace/reallysimpledocs/src/runtime/pages/doc.astro`
- `/Users/hunvreus/Workspace/reallysimpledocs/src/runtime/components/DocsLayout.astro`
- `/Users/hunvreus/Workspace/reallysimpledocs/src/runtime/lib/docs.js`
- RSD does not currently support MDX, Astro components inside docs Markdown, or React islands inside docs content.
- RSD does load Basecoat CSS/JS for generated docs pages, so raw Basecoat HTML works.
- Basecoat already has a richer docs source flow:
- source docs under `/Users/hunvreus/Workspace/basecoat/docs/src`
- Nunjucks macros under `/Users/hunvreus/Workspace/basecoat/docs/src/_includes/macros`
- generated RSD-compatible docs under `/Users/hunvreus/Workspace/basecoat/docs/generated`
- generator: `/Users/hunvreus/Workspace/basecoat/scripts/generate-rsd-docs.js`
- Basecoat depends on RSD through `package.json`: `reallysimpledocs: "file:../reallysimpledocs"`.
- Basecoat docs source is already in `docs/src` and is now mostly `.mdx`.
- RSD now supports Markdown and MDX pages from `docsDir`, with default MDX components available without imports.
- Basecoat config already points RSD at source docs:
- `docsDir: "./docs/src"`
- `routeBase: "/"`
- `style: "vega"`
- `components.Head: "./docs/src/site/StyleHead.astro"`
- `components.ContentHeader: "./docs/src/site/ContentHeader.astro"`
- Latest RSD no longer has a `js` option. It always inserts managed JavaScript for theme initialization, Basecoat JS, copy-code behavior, copy-page behavior, search, and TOC state.
- The old generated-docs flow is no longer the main path. `docs/generated/**` is ignored and should not be hand-edited.
## Existing Basecoat pattern
## Current blocker
Basecoat currently authors examples with Nunjucks and renders them to portable Markdown/HTML before RSD sees them.
`npm run docs:build` currently fails after `npm run build` succeeds.
Example source:
Observed failure:
```njk
{% set code_html %}
{{ tabs(
id="demo-tabs-with-panels",
tabsets=tabsets_demo,
main_attrs={ "class": "w-full" }
) }}
{% endset %}
{{ code_preview("tabs", code_html | prettyHtml, "max-w-[400px] w-full") }}
```text
[@tailwindcss/vite:generate:build] Can't resolve
/Users/hunvreus/Workspace/basecoat/node_modules/reallysimpledocs/src/css/components.css
in /Users/hunvreus/Workspace/basecoat/.astro/reallysimpledocs/styles.css
```
Relevant macro:
Cause:
- `/Users/hunvreus/Workspace/basecoat/docs/src/_includes/macros/code_preview.njk`
- `astro.config.mjs` has a custom `basecoatDocsStyles()` integration that writes `.astro/reallysimpledocs/styles.css`.
- That custom stylesheet imports private RSD internals that no longer exist in latest RSD:
- `node_modules/reallysimpledocs/src/css/components.css`
- `node_modules/reallysimpledocs/src/css/sources.css`
- Basecoat style-switcher assets also import removed private RSD style files:
- `docs/src/assets/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
- each imports `node_modules/reallysimpledocs/src/css/styles/<style>.css`
That macro renders:
Latest RSD no longer owns Basecoat style files. With managed CSS enabled, it imports public `basecoat-css` entrypoints and its own small CSS files:
- a Basecoat `tabs` component
- a Preview tab containing rendered HTML
- a Code tab containing escaped source
- no React, no MDX, no client hydration beyond Basecoat JS
- `basecoat-css/base`
- `basecoat-css/styles/<style>`
- `reallysimpledocs/css`
- `reallysimpledocs/css/styles/<style>`
This is good for Basecoat because Basecoat is framework-agnostic and already ships Nunjucks/Jinja macros.
Because Basecoat needs live style switching, Basecoat docs should set `css: false` and own the full docs stylesheet pipeline. Keep RSD-managed JavaScript on; there is no `js: false` option anymore.
## Decision so far
## Migration target
Nunjucks is not Astro-native.
Basecoat should stop depending on private RSD source CSS paths except, if strictly necessary, RSD's own stable small CSS files.
For a generic Astro site using RSD, the Astro-native options are:
The docs site should use:
- `.astro` components
- MDX for components inside docs
- optional framework islands only when needed
- Basecoat's own generated style bundles for Basecoat component visuals.
- `css: false` in the RSD integration, so Basecoat controls docs CSS and style switching.
- RSD-managed JavaScript, always on.
- RSD's integration for routing, layout, MDX, search, Markdown exports, `llms.txt`, and `llms-full.txt`.
- RSD component overrides for site-specific chrome.
- No `site.repository`-style UI metadata. Header actions belong in `ContentHeader`.
For RSD itself, do not make React the default path. It would pull RSD away from the current lightweight, portable Markdown model.
## Work plan
## Likely best direction
### 1. Remove the custom RSD stylesheet writer
Keep RSD core simple:
Edit `astro.config.mjs`.
- Markdown pages
- generated HTML
- Basecoat JS
- search, Markdown exports, `llms.txt`, and `llms-full.txt`
Remove:
Then add richer authoring as an optional layer.
- `fs`, `path`, and `fileURLToPath` imports if they become unused.
- `rsdCssPath()`.
- `basecoatDocsStyles()`.
- `basecoatDocsStyles()` from `integrations`.
Two viable layers:
Do not write `.astro/reallysimpledocs/styles.css` manually. Latest RSD already generates that virtual stylesheet.
1. **Basecoat-specific generated docs flow**
- Keep using Nunjucks macros and `scripts/generate-rsd-docs.js`.
- Improve macros as needed.
- Output remains plain Markdown/HTML consumed by RSD.
- Best for Basecoat itself.
Keep the RSD integration options:
2. **Astro-native optional RSD examples API**
- Add optional MDX support later.
- Provide components such as `Preview.astro`, `CodeBlock.astro`, `Callout.astro`, maybe `Tabs.astro`.
- Prefer file-based examples so source and rendered output stay in sync.
Preferred Astro-native example shape:
```mdx
import Preview from "reallysimpledocs/components/Preview.astro";
import ButtonDemo from "../examples/ButtonDemo.astro";
import buttonSource from "../examples/ButtonDemo.astro?raw";
<Preview code={buttonSource}>
<ButtonDemo />
</Preview>
```js
reallySimpleDocs({
docsDir: "./docs/src",
routeBase: "/",
css: false,
components: {
Head: "./docs/src/site/StyleHead.astro",
ContentHeader: "./docs/src/site/ContentHeader.astro",
},
site: { ... },
})
```
This is more Astro-native than Nunjucks, but it requires RSD to support MDX or an Astro content path. It also makes docs less portable than plain Markdown.
### 2. Move Basecoat docs CSS to owned style assets
## Avoid for now
Do not let RSD generate the docs stylesheet for Basecoat. Set `css: false`, then make `docs/src/assets/styles/style-*.css` the full docs stylesheets that `StyleHead.astro` swaps.
- Do not add React just for examples.
- Do not make MDX the default content model.
- Do not invent directive syntax such as:
Do not import removed RSD CSS files from `docs/src/assets/styles/style-*.css`.
```md
::preview{class="max-w-sm"}
...
::
```
Recommended target for each style asset:
RSD uses `marked` today, so directive syntax would require another parser or custom extension. It is not currently supported.
```css
@import "tailwindcss" source(none);
@reference "tailwindcss";
@reference "../../../../src/css/base/base.css";
@import "../../../../src/css/basecoat-vega.css";
@import "reallysimpledocs/css";
@import "reallysimpledocs/css/styles/vega";
```
## Open questions
Use the matching `basecoat-<style>.css` file for each style.
Use the matching `reallysimpledocs/css/styles/<style>` file for RSD's style-specific preview and code-surface rules.
- Should Basecoat keep source docs in Nunjucks permanently, or move docs authoring to Astro/MDX once RSD is Astro-first?
- Should RSD expose `Preview.astro` and related components as public package API?
- If RSD supports MDX later, should that be:
- an opt-in mode,
- a separate integration option,
- or a separate package?
- How should source-code extraction work?
- explicit `code` prop,
- file import with `?raw`,
- or a build-time plugin that captures source from children?
Remove this line from every style asset:
## Suggested next steps
```css
@import "../../../../node_modules/reallysimpledocs/src/css/styles/<style>.css";
```
1. Keep Basecoat's existing Nunjucks generator working for the current RSD beta.
2. Improve `code_preview.njk` only if the rendered RSD output needs better Basecoat markup or accessibility.
3. In RSD, do not add React or MDX until there is a concrete consumer need.
4. If richer Astro-native examples become necessary, prototype a small `Preview.astro` with file-based `?raw` source imports.
5. Validate any prototype against:
- RSD static build
- search index output
- Markdown exports
- `llms.txt` / `llms-full.txt`
- Basecoat JS behavior
`reallysimpledocs/css` is the public shared RSD CSS export. `reallysimpledocs/css/styles/<style>` provides the matching RSD style-specific CSS without importing Basecoat again.
## Validation commands
Do not reintroduce removed `components.css`, `sources.css`, or `styles/*`.
For Basecoat generated docs:
### 3. Keep the style switcher, but make it compatible with latest RSD
Files:
- `docs/src/site/StyleHead.astro`
- `docs/src/site/ContentHeader.astro`
- `src/pages/index.astro`
Current behavior:
- Loads one docs style asset based on `localStorage.styleVariant`.
- Adds `data-style-variant` to `<html>`.
- Swaps `<link id="docs-style-variant">`.
Keep this behavior for Basecoat's style demo. With `css: false`, RSD's `style` option is unnecessary for Basecoat docs; Basecoat's style switcher owns the active stylesheet.
Audit duplication:
- Make sure the page does not load two competing full Basecoat style bundles in a way that makes non-Vega styles override Vega instead of standing alone.
- If duplicates are unavoidable during migration, document the temporary state in this file before handing off.
### 4. Fix navigation shape only if RSD rejects it
`docs/src/docs.json` currently uses item objects with `type: "item"`.
Latest RSD accepts page objects by `slug` and link objects by `url`; it does not require `type: "item"` for page/link entries based on current docs. Do not mass-rewrite navigation unless the build or runtime proves it is needed.
If there is a failure, normalize entries to:
```json
{ "slug": "installation", "label": "Installation", "icon": "download" }
```
and external links to:
```json
{ "label": "GitHub", "url": "https://github.com/hunvreus/basecoat", "icon": "github" }
```
Note: existing inline SVG icons in `docs.json` may or may not be supported by current RSD's icon pipeline. Verify before changing.
### 5. Verify MDX docs against current RSD components
Basecoat docs use RSD components heavily:
- `Preview`
- `Code`
- `CodeGroup`
- `Callout`
- `Steps`
- `Step`
Latest RSD makes common components available by default in MDX. Do not add imports unless a page fails.
Known special case:
- `docs/src/components/chart.mdx` imports `../site/chart-examples.js?url`.
Verify that MDX import still works after CSS fixes.
### 6. Validate generated outputs and routes
Run from `/Users/hunvreus/Workspace/basecoat`:
```bash
cd /Users/hunvreus/Workspace/basecoat
npm run build
npm run docs:build
```
For RSD package behavior:
Expected docs outputs include:
- `/index.html`
- component pages under `/components/.../index.html`
- `/search-index.json`
- per-page Markdown exports such as `/installation.md`
- `/llms.txt`
- `/llms-full.txt`
Then run:
```bash
cd /Users/hunvreus/Workspace/reallysimpledocs
npm run check
npm run docs:dev
```
Browser-check:
- desktop and mobile widths
- search dialog
- Markdown/AI links
- style switcher for all eight styles
- dark mode
- component previews using JS behavior:
- dropdown menu
- select
- combobox
- command
- dialog
- tabs
- toast
- chart
### 7. Clean up stale docs once build passes
Update or remove outdated handoff text that says RSD does not support MDX. This file has already been updated for the latest RSD state.
Do not edit generated docs:
- `docs/generated/**`
- `dist/**`
- `packages/*/dist/**`
If build scripts regenerate committed source CSS entrypoints, use the scripts and review the generated source diffs before committing.
## Files likely touched
- `astro.config.mjs`
- `docs/src/assets/styles/style-vega.css`
- `docs/src/assets/styles/style-nova.css`
- `docs/src/assets/styles/style-maia.css`
- `docs/src/assets/styles/style-lyra.css`
- `docs/src/assets/styles/style-mira.css`
- `docs/src/assets/styles/style-luma.css`
- `docs/src/assets/styles/style-sera.css`
- `docs/src/assets/styles/style-rhea.css`
- Maybe `docs/src/site/StyleHead.astro`
- Maybe `docs/src/site/ContentHeader.astro`
- Maybe `docs/src/docs.json`
## Do not do
- Do not restore `basecoatDocsStyles()` by copying newer RSD internals.
- Do not import RSD files that are not public or intentionally stable.
- Do not add or use `js: false`; latest RSD always manages JavaScript.
- Do not reintroduce a generated `docs/generated` pipeline unless explicitly requested.
- Do not put GitHub/header actions into `site` metadata.
- Do not hand-edit build outputs.
## Open questions for the implementation agent
- Should inline SVG icons in `docs/src/docs.json` be kept, or should they move to Lucide icon names plus header/footer overrides?
## Recommended first patch
Start with the smallest build-fixing patch:
1. Delete `basecoatDocsStyles()` from `astro.config.mjs`.
2. Add `css: false` to the RSD integration options.
3. Remove all `node_modules/reallysimpledocs/src/css/styles/<style>.css` imports from `docs/src/assets/styles/style-*.css`.
4. Import Basecoat's own full style bundle in each style asset, plus RSD's own CSS through a public export if available.
5. Run `npm run docs:build`.
6. If layout CSS is missing, fix that through a public RSD CSS export instead of private source imports.
+1 -1
View File
@@ -125,7 +125,7 @@ async function build() {
// Create combined component files
console.log('Creating combined component files...');
const componentsToCombine = ['basecoat.js', 'command.js', 'combobox.js', 'dropdown-menu.js', 'popover.js', 'range.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
const componentsToCombine = ['basecoat.js', 'accordion.js', 'command.js', 'combobox.js', 'dropdown-menu.js', 'popover.js', 'range.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
const componentPaths = componentsToCombine.map(f => path.join(srcJsDir, f));
// Create non-minified bundle
+2
View File
@@ -6,9 +6,11 @@ const excludedComponents = new Set(['form']);
// Keep cascade stable for components whose selectors intentionally compose.
const componentOrder = [
'accordion',
'alert',
'avatar',
'badge',
'breadcrumb',
'button',
'button-group',
'card',
+1 -1
View File
@@ -50,7 +50,7 @@
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.269 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
+2
View File
@@ -1,7 +1,9 @@
/* Components */
@import "./components/accordion.css";
@import "./components/alert.css";
@import "./components/avatar.css";
@import "./components/badge.css";
@import "./components/breadcrumb.css";
@import "./components/button.css";
@import "./components/button-group.css";
@import "./components/card.css";
+27
View File
@@ -0,0 +1,27 @@
/* Accordion */
@layer components {
.accordion {
@apply flex w-full flex-col;
}
.accordion > details > summary {
@apply relative flex w-full flex-1 cursor-pointer list-none items-start justify-between border border-transparent transition-all outline-none;
}
.accordion > details > summary > svg:last-child {
@apply pointer-events-none shrink-0 transition-transform;
}
.accordion > details[open] > summary > svg:last-child {
@apply rotate-180;
}
.accordion > details[aria-disabled='true'] > summary,
.accordion > details > summary[aria-disabled='true'] {
@apply pointer-events-none opacity-50;
}
.accordion > details > :not(summary) {
@apply overflow-hidden;
}
}
+12
View File
@@ -15,9 +15,21 @@
.alert > section {
@apply grid justify-items-start gap-1;
:where(p, ul, ol) {
@apply m-0;
}
:where(p, ul, ol):not(:last-child) {
@apply mb-4;
}
ul {
@apply list-inside list-disc;
}
ol {
@apply list-inside list-decimal;
}
}
.alert > footer {
+6 -6
View File
@@ -8,27 +8,27 @@
@apply absolute inset-0 aspect-square size-full rounded-[inherit] object-cover;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply flex size-full items-center justify-center rounded-[inherit] text-sm;
}
.avatar[data-size='sm'] > span:not([data-badge]) {
.avatar[data-size='sm'] > span:not(.avatar-badge) {
@apply text-xs;
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply absolute end-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none;
}
.avatar[data-size='sm'] > [data-badge] {
.avatar[data-size='sm'] > .avatar-badge {
@apply size-2 [&>svg]:hidden;
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply size-2.5 [&>svg]:size-2;
}
.avatar[data-size='lg'] > [data-badge] {
.avatar[data-size='lg'] > .avatar-badge {
@apply size-3 [&>svg]:size-2;
}
+30
View File
@@ -0,0 +1,30 @@
/* Breadcrumb */
@layer components {
.breadcrumb > ol {
@apply m-0 flex list-none flex-wrap items-center p-0 break-words;
}
.breadcrumb li {
@apply inline-flex items-center;
}
.breadcrumb a {
@apply rounded-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50;
}
.breadcrumb [aria-current='page'] {
@apply cursor-default;
}
.breadcrumb li[aria-hidden='true'] {
@apply select-none;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply flex items-center justify-center;
}
.breadcrumb [data-rtl-flip] {
@apply rtl:rotate-180;
}
}
+4
View File
@@ -15,6 +15,10 @@
@apply col-start-2 row-span-2 row-start-1 self-start justify-self-end;
}
> header > menu.card-action {
@apply m-0 list-none p-0;
}
> footer {
@apply flex items-center;
}
+23 -2
View File
@@ -8,18 +8,39 @@
@apply w-full min-w-0 outline-hidden placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
}
.combobox .input-group input[role='combobox'],
.combobox [data-popover] input[role='combobox'] {
@apply min-w-0 outline-hidden placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50;
}
.combobox > svg,
.combobox-trigger-icon {
@apply pointer-events-none absolute shrink-0;
}
.combobox:has(> .combobox-chips) > .combobox-trigger-icon {
@apply hidden;
}
.combobox [data-clear] {
@apply inline-flex size-6 shrink-0 cursor-default appearance-none items-center justify-center border-0 bg-transparent p-0 text-current disabled:pointer-events-none disabled:opacity-50;
}
.combobox [data-clear][hidden] {
@apply hidden;
}
.combobox .input-group:has([data-clear]:not([hidden])) button[aria-haspopup='listbox'] {
@apply hidden;
}
.combobox [data-popover] {
@apply isolate overflow-hidden;
min-width: max(100%, 9rem);
}
.combobox [role='listbox'] {
@apply outline-hidden overflow-y-auto;
@apply outline-hidden overflow-x-hidden overflow-y-auto;
}
.combobox [role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@@ -73,6 +94,6 @@
}
.combobox-chip-remove {
@apply inline-flex items-center justify-center;
@apply inline-flex shrink-0 items-center justify-center;
}
}
+19 -12
View File
@@ -12,7 +12,7 @@
@apply opacity-100;
}
> * {
> .command {
@apply scale-100;
}
@@ -23,7 +23,7 @@
@apply opacity-0;
}
> * {
> .command {
@apply scale-95;
}
}
@@ -33,19 +33,19 @@
@apply opacity-0 transition-all transition-discrete;
}
> * {
@apply fixed top-1/3 left-1/2 z-50 flex max-h-[min(640px,calc(100%_-_2rem))] w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 translate-y-0 scale-95 flex-col overflow-hidden transition-all sm:max-w-lg;
> .command {
@apply fixed top-1/3 left-1/2 z-50 flex max-h-[min(640px,calc(100%_-_2rem))] w-full max-w-[calc(100%_-_2rem)] -translate-x-1/2 translate-y-0 scale-95 flex-col overflow-hidden transition-all sm:max-w-sm;
}
> * > :is(button, form[method='dialog']) {
> .command > :is(button, form[method='dialog']) {
@apply absolute;
}
> * > :is(button, form[method='dialog'] > button) {
> .command > :is(button, form[method='dialog'] > button) {
@apply inline-flex items-center justify-center opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
> * > :is(button, form[method='dialog'] > button) > svg:not([class*='size-']) {
> .command > :is(button, form[method='dialog'] > button) > svg:not([class*='size-']) {
@apply size-4;
}
}
@@ -68,14 +68,20 @@
.command [role='menu'] {
@apply overflow-x-hidden overflow-y-auto outline-hidden;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.command [role='menuitem'] {
@apply aria-hidden:hidden relative flex w-full cursor-default items-center truncate outline-hidden select-none disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
@apply aria-hidden:hidden relative flex w-full cursor-default items-center outline-hidden select-none disabled:pointer-events-none disabled:opacity-50 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground;
.command [role='menuitem'].active,
.command [role='menuitem'][data-selected='true'] {
@apply bg-muted text-foreground;
}
.command [role='group'] {
@@ -103,7 +109,8 @@
}
.command [role='menuitem'][data-checked='true'] [data-indicator],
.command [role='menuitem'][aria-selected='true'] [data-indicator] {
.command [role='menuitem'][aria-selected='true'] [data-indicator],
.command [role='menuitem'][data-selected='true'] [data-indicator] {
@apply opacity-100;
}
@@ -124,7 +131,7 @@
}
.command:not([data-command-initialized]) [role='menuitem']:not([aria-disabled='true']):not(:disabled):hover {
@apply bg-accent text-accent-foreground;
@apply bg-muted text-foreground;
}
.command:has(> header input:not(:placeholder-shown)) [role='separator'] {
+30 -1
View File
@@ -1,6 +1,7 @@
/* Dialog */
@layer components {
.dialog {
.dialog,
.alert-dialog {
@apply bg-transparent p-0 text-inherit opacity-0 transition-all transition-discrete backdrop:opacity-0 backdrop:transition-all backdrop:transition-discrete;
max-width: none;
max-height: none;
@@ -24,7 +25,9 @@
> * {
@apply fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100%_-_2rem)] w-full -translate-x-1/2 -translate-y-1/2 scale-95 flex-col transition-all outline-hidden;
}
}
.dialog {
> * > header {
@apply flex flex-col;
}
@@ -49,4 +52,30 @@
@apply size-4;
}
}
.alert-dialog {
> * > header {
@apply grid grid-rows-[auto_1fr] place-items-center text-center sm:place-items-start sm:text-start;
}
> * > header:has(> figure) {
@apply grid-rows-[auto_auto_1fr] sm:grid-rows-[auto_1fr];
}
> * > header > figure {
@apply m-0 shrink-0 sm:row-span-2 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
> * > header > figure:has(img) {
@apply overflow-hidden [&_img]:size-full [&_img]:object-cover;
}
> * > header:has(> figure) > :is(h2, p) {
@apply sm:col-start-2;
}
> * > footer {
@apply flex flex-col-reverse sm:flex-row sm:justify-end;
}
}
}
+1 -1
View File
@@ -33,7 +33,7 @@
}
.dropdown-menu :is(kbd:last-child, .kbd:last-child):not([data-shortcut]) {
@apply ms-8;
@apply ps-8;
}
.dropdown-menu [data-indicator] {
-44
View File
@@ -1,44 +0,0 @@
@layer theme, base, components, utilities;
@import "tailwindcss" source("../pages");
.theme-neutral {
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
}
:root {
--landing-demo-surface-radius: 0.375rem;
--chart-bar-radius: 6px;
}
[data-style-variant="nova"] {
--landing-demo-surface-radius: 0.5rem;
}
[data-style-variant="maia"],
[data-style-variant="luma"],
[data-style-variant="rhea"] {
--landing-demo-surface-radius: 1rem;
}
[data-style-variant="lyra"],
[data-style-variant="sera"] {
--landing-demo-surface-radius: 0;
--chart-bar-radius: 0;
}
.landing-demo-surface {
border-radius: var(--landing-demo-surface-radius);
}
.theme-neutral .card > footer,
.theme-neutral hr {
border-color: var(--border);
}
.landing-demo-chart-bar {
border-start-start-radius: var(--chart-bar-radius);
border-start-end-radius: var(--chart-bar-radius);
}
+98 -16
View File
@@ -1,5 +1,26 @@
/* Button */
/* Accordion */
@layer components {
.accordion {
@apply overflow-hidden rounded-2xl border;
}
.accordion > details {
@apply open:bg-muted/50 not-last:border-b;
}
.accordion > details > summary {
@apply gap-6 p-4 text-left text-sm font-medium hover:underline;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply px-4 pt-0 pb-4 text-sm;
}
/* Button */
.btn {
@apply rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -255,7 +276,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -263,7 +284,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -309,17 +330,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-4xl p-1;
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-sm sm:gap-2.5;
}
.command-dialog > * {
@apply rounded-4xl p-0;
.breadcrumb li {
@apply gap-1.5;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-4xl! p-1;
}
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 rounded-4xl! shadow-xl ring-1 duration-100;
}
.command > header {
@apply m-1 mb-0 h-9 gap-2 rounded-3xl bg-input/50 px-3;
@apply m-1 mb-0 h-9 gap-0 rounded-3xl bg-input/50 px-3;
}
.command > header svg {
@@ -327,7 +373,7 @@
}
.command > header input {
@apply h-full text-sm placeholder:text-muted-foreground;
@apply ps-1.5 text-sm placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -355,7 +401,7 @@
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -378,11 +424,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-h-72 rounded-3xl p-1.5 shadow-lg ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-h-72 gap-0 rounded-3xl p-0 shadow-lg ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1.5 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1.5 overflow-y-auto p-1.5;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -399,7 +449,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -421,6 +471,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1.5 mb-0 h-8 w-auto border-input/30 bg-input/50 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply bg-input/50 border-transparent focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 data-[size=sm]:h-8 gap-1.5 rounded-3xl border px-3 py-2 text-sm transition-[color,box-shadow,background-color] focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4;
@@ -473,10 +531,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/30 backdrop-blur-sm duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-w-xs gap-6 rounded-4xl p-6 shadow-xl ring-1 duration-100 sm:max-w-md;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-w-[calc(100%-2rem)] gap-6 rounded-4xl p-6 text-sm shadow-xl ring-1 duration-100 sm:max-w-md;
}
@@ -493,6 +556,15 @@
@apply gap-1.5;
}
.alert-dialog > * > header {
@apply gap-x-6 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-full [&>svg:not([class*='size-'])]:size-8;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -502,11 +574,21 @@
@apply text-base leading-none font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-lg font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-sm leading-normal [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 min-w-48 gap-0 overflow-x-hidden overflow-y-auto rounded-3xl p-1.5 shadow-lg ring-1 duration-100;
@@ -553,11 +635,11 @@
@apply bg-border/50 -mx-1.5 my-1.5 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+87 -13
View File
@@ -1,5 +1,22 @@
/* Button */
/* Accordion */
@layer components {
.accordion > details {
@apply not-last:border-b;
}
.accordion > details > summary {
@apply focus-visible:ring-ring/50 focus-visible:border-ring rounded-none py-2.5 text-left text-xs font-medium hover:underline focus-visible:ring-1;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply pt-0 pb-2.5 text-xs;
}
/* Button */
.btn {
@apply rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[1px] aria-invalid:border-destructive aria-invalid:ring-[1px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -247,7 +264,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -255,7 +272,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -301,17 +318,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-xs;
}
.breadcrumb li {
@apply gap-1;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-none;
}
.command-dialog > * {
@apply rounded-none p-0;
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/10 rounded-none ring-1 duration-100;
}
.command > header {
@apply h-8 gap-2 border-b bg-input/30 px-2;
@apply h-8 gap-0 border-b bg-input/30 px-2;
}
.command > header svg {
@@ -319,7 +361,7 @@
}
.command > header input {
@apply h-full text-xs placeholder:text-muted-foreground;
@apply ps-1.5 text-xs placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -347,7 +389,7 @@
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -361,7 +403,7 @@
/* Combobox */
.combobox > input[role='combobox'] {
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 rounded-none border bg-transparent py-2 ps-2.5 pe-8 text-xs transition-colors focus-visible:ring-1 aria-invalid:ring-1;
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent py-2 ps-2.5 pe-8 text-xs transition-colors focus-visible:ring-1 aria-invalid:ring-1;
}
.combobox > svg,
@@ -370,7 +412,7 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 rounded-none shadow-md ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 gap-0 rounded-none p-0 shadow-md ring-1 duration-100;
}
.combobox [role='listbox'] {
@@ -391,7 +433,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='separator'] {
@@ -409,6 +451,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1 mb-0 h-8 w-auto border-input/30 bg-input/30 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply border-input dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 data-[size=sm]:h-7 gap-1.5 rounded-none data-[size=sm]:rounded-none border bg-transparent py-2 ps-2.5 pe-2 text-xs transition-colors select-none focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4;
@@ -461,10 +511,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-xs gap-4 rounded-none p-4 ring-1 duration-100 sm:max-w-sm;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-[calc(100%-2rem)] gap-4 rounded-none p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm;
}
@@ -477,6 +532,15 @@
@apply gap-1 text-start;
}
.alert-dialog > * > header {
@apply gap-x-4 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-none [&>svg:not([class*='size-'])]:size-6;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -486,11 +550,21 @@
@apply text-sm font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-sm font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-xs/relaxed [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-xs/relaxed text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 gap-0 overflow-x-hidden overflow-y-auto rounded-none p-0 shadow-md ring-1 duration-100;
@@ -537,11 +611,11 @@
@apply bg-border -mx-1 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+98 -16
View File
@@ -1,5 +1,26 @@
/* Button */
/* Accordion */
@layer components {
.accordion {
@apply overflow-hidden rounded-2xl border;
}
.accordion > details {
@apply open:bg-muted/50 not-last:border-b;
}
.accordion > details > summary {
@apply gap-6 p-4 text-left text-sm font-medium hover:underline;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply px-4 pt-0 pb-4 text-sm;
}
/* Button */
.btn {
@apply rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -247,7 +268,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -255,7 +276,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -301,17 +322,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-4xl p-1;
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-sm sm:gap-2.5;
}
.command-dialog > * {
@apply rounded-4xl p-0;
.breadcrumb li {
@apply gap-1.5;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-4xl! p-1;
}
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/5 rounded-4xl! ring-1 duration-100;
}
.command > header {
@apply m-1 mb-0 h-9 gap-2 rounded-3xl bg-input/30 px-3;
@apply m-1 mb-0 h-9 gap-0 rounded-3xl bg-input/30 px-3;
}
.command > header svg {
@@ -319,7 +365,7 @@
}
.command > header input {
@apply h-full text-sm placeholder:text-muted-foreground;
@apply ps-1.5 text-sm placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -347,7 +393,7 @@
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -370,11 +416,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/5 max-h-72 rounded-2xl p-1 shadow-2xl ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/5 max-h-72 gap-0 rounded-2xl p-0 shadow-2xl ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto p-1;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -391,7 +441,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -413,6 +463,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1 mb-0 h-9 w-auto border-none bg-input/30 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply border-input bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 data-[size=sm]:h-8 gap-1.5 rounded-4xl border px-3 py-2 text-sm transition-colors focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4;
@@ -465,10 +523,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/80 backdrop-blur-xs duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/5 max-w-xs gap-6 rounded-4xl p-6 ring-1 duration-100 sm:max-w-md;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/5 max-w-[calc(100%-2rem)] gap-6 rounded-4xl p-6 text-sm ring-1 duration-100 sm:max-w-md;
}
@@ -481,6 +544,15 @@
@apply gap-2;
}
.alert-dialog > * > header {
@apply gap-x-6 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-full [&>svg:not([class*='size-'])]:size-8;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -490,11 +562,21 @@
@apply text-base leading-none font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-lg font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-sm leading-normal [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 min-w-48 gap-0 overflow-x-hidden overflow-y-auto rounded-2xl p-1 shadow-2xl ring-1 duration-100;
@@ -541,11 +623,11 @@
@apply bg-border/50 -mx-1 my-1 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+98 -16
View File
@@ -1,5 +1,26 @@
/* Button */
/* Accordion */
@layer components {
.accordion {
@apply overflow-hidden rounded-md border;
}
.accordion > details {
@apply open:bg-muted/50 not-last:border-b;
}
.accordion > details > summary {
@apply gap-6 p-2 text-left text-xs/relaxed font-medium hover:underline;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply px-2 pt-0 pb-4 text-xs/relaxed;
}
/* Button */
.btn {
@apply rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[2px] aria-invalid:border-destructive aria-invalid:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -247,7 +268,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -255,7 +276,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -301,17 +322,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-xl p-1;
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-xs/relaxed;
}
.command-dialog > * {
@apply rounded-xl p-0;
.breadcrumb li {
@apply gap-1;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-4 [&>svg]:size-3.5;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-xl! p-1;
}
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/10 rounded-xl! ring-1 duration-100;
}
.command > header {
@apply m-1 mb-0 h-8 gap-2 rounded-lg bg-input/20 px-2 dark:bg-input/30;
@apply m-1 mb-0 h-8 gap-0 rounded-lg bg-input/20 px-2 dark:bg-input/30;
}
.command > header svg {
@@ -319,7 +365,7 @@
}
.command > header input {
@apply h-full text-xs/relaxed placeholder:text-muted-foreground;
@apply ps-1.5 text-xs/relaxed placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -347,7 +393,7 @@
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -370,11 +416,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 dark:bg-popover max-h-72 rounded-lg p-1 shadow-md ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/10 dark:bg-popover max-h-72 gap-0 rounded-lg p-0 shadow-md ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto p-1;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -391,7 +441,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -413,6 +463,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3;
}
.combobox [data-popover] > .input-group {
@apply m-1 mb-0 h-7 w-auto border-none bg-input/20 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply border-input bg-input/20 dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-7 data-[size=sm]:h-6 data-[size=sm]:text-[0.625rem] gap-1.5 rounded-md border px-2 py-1.5 text-xs/relaxed transition-colors focus-visible:ring-2 aria-invalid:ring-2 [&_svg:not([class*='size-'])]:size-3.5;
@@ -469,10 +527,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/80 backdrop-blur-xs duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-xs gap-3 rounded-xl p-4 ring-1 duration-100 sm:max-w-sm;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm;
}
@@ -485,6 +548,15 @@
@apply gap-1;
}
.alert-dialog > * > header {
@apply gap-x-4 gap-y-1;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-8 items-center justify-center rounded-md [&>svg:not([class*='size-'])]:size-4;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -494,11 +566,21 @@
@apply text-sm font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-sm font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-xs/relaxed [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-xs/relaxed text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 gap-0 overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100;
@@ -545,11 +627,11 @@
@apply bg-border/50 -mx-1 my-1 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-[0.625rem] tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+96 -18
View File
@@ -1,5 +1,22 @@
/* Button */
/* Accordion */
@layer components {
.accordion > details {
@apply not-last:border-b;
}
.accordion > details > summary {
@apply focus-visible:ring-ring/50 focus-visible:border-ring rounded-lg py-2.5 text-left text-sm font-medium hover:underline focus-visible:ring-3;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply pt-0 pb-2.5 text-sm;
}
/* Button */
.btn {
@apply rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -247,7 +264,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -255,7 +272,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -301,17 +318,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-xl p-1;
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-sm;
}
.command-dialog > * {
@apply rounded-xl p-0;
.breadcrumb li {
@apply gap-1;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-xl! p-1;
}
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/10 rounded-xl! ring-1 duration-100;
}
.command > header {
@apply m-1 mb-0 h-8 gap-2 rounded-lg border border-input/30 bg-input/30 px-2;
@apply m-1 mb-0 h-8 gap-0 rounded-lg border border-input/30 bg-input/30 px-2;
}
.command > header svg {
@@ -319,7 +361,7 @@
}
.command > header input {
@apply h-full text-sm placeholder:text-muted-foreground;
@apply ps-1.5 text-sm placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -343,11 +385,11 @@
}
.command [role='menuitem'] {
@apply gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4;
@apply gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 in-[.command-dialog]:rounded-lg;
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -361,7 +403,7 @@
/* Combobox */
.combobox > input[role='combobox'] {
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 rounded-lg border bg-transparent py-1 ps-2.5 pe-8 text-sm transition-colors focus-visible:ring-3 aria-invalid:ring-3;
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent py-1 ps-2.5 pe-8 text-sm transition-colors focus-visible:ring-3 aria-invalid:ring-3;
}
.combobox > svg,
@@ -370,11 +412,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 rounded-lg p-1 shadow-md ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 gap-0 rounded-lg p-0 shadow-md ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto p-1;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -391,7 +437,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -413,6 +459,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1 mb-0 h-8 w-auto border-input/30 bg-input/30 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply border-input dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 data-[size=sm]:h-7 gap-1.5 rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] border bg-transparent py-2 ps-2.5 pe-2 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4;
@@ -465,10 +519,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-xs gap-4 rounded-xl p-4 ring-1 duration-100 sm:max-w-sm;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm;
}
@@ -481,6 +540,15 @@
@apply gap-2;
}
.alert-dialog > * > header {
@apply gap-x-4 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md [&>svg:not([class*='size-'])]:size-6;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply bg-muted/50 -mx-4 -mb-4 gap-2 rounded-b-xl border-t p-4;
}
@@ -490,11 +558,21 @@
@apply text-base leading-none font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-base font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-sm leading-normal [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 gap-0 overflow-x-hidden overflow-y-auto rounded-lg p-1 shadow-md ring-1 duration-100;
@@ -541,11 +619,11 @@
@apply bg-border -mx-1 my-1 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+102 -20
View File
@@ -1,5 +1,26 @@
/* Button */
/* Accordion */
@layer components {
.accordion {
@apply overflow-hidden rounded-2xl border;
}
.accordion > details {
@apply open:bg-muted/50 not-last:border-b;
}
.accordion > details > summary {
@apply gap-6 p-4 text-left text-sm font-medium hover:underline;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply px-4 pt-0 pb-4 text-sm;
}
/* Button */
.btn {
@apply rounded-2xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -255,7 +276,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -263,7 +284,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -309,17 +330,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-3xl p-1;
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-sm sm:gap-2.5;
}
.command-dialog > * {
@apply rounded-3xl p-0;
.breadcrumb li {
@apply gap-1.5;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-3xl! p-1;
}
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 rounded-3xl! shadow-xl ring-1 duration-100;
}
.command > header {
@apply m-1 mb-0 h-8 gap-2 rounded-2xl bg-input/50 px-2;
@apply m-1 mb-0 h-8 gap-0 rounded-2xl bg-input/50 px-2;
}
.command > header svg {
@@ -327,7 +373,7 @@
}
.command > header input {
@apply h-full text-sm placeholder:text-muted-foreground;
@apply ps-1.5 text-sm placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -355,7 +401,7 @@
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -378,11 +424,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-h-72 rounded-2xl p-1 shadow-lg ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-h-72 gap-0 rounded-2xl p-0 shadow-lg ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto p-1;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -399,7 +449,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -421,6 +471,14 @@
.combobox-chip-remove {
@apply -ms-0.5 size-4.5 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1 mb-0 h-8 w-auto border-input/30 bg-input/50 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply bg-input/50 border-transparent focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 data-[size=sm]:h-7 gap-1.5 rounded-2xl border px-3 py-2 text-sm transition-[color,box-shadow] duration-200 focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4;
@@ -473,12 +531,17 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/30 backdrop-blur-sm duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-w-xs gap-6 rounded-3xl p-6 shadow-xl ring-1 duration-100 sm:max-w-md;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-w-[calc(100%-2rem)] gap-6 rounded-[min(var(--radius-4xl),24px)] p-6 text-sm shadow-xl ring-1 duration-100 sm:max-w-md;
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 max-w-[calc(100%-2rem)] gap-6 rounded-3xl p-6 text-sm shadow-xl ring-1 duration-100 sm:max-w-md;
}
.dialog > * > :is(button, form[method='dialog']) {
@@ -493,6 +556,15 @@
@apply gap-1.5;
}
.alert-dialog > * > header {
@apply gap-x-6 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-full [&>svg:not([class*='size-'])]:size-8;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -502,11 +574,21 @@
@apply text-base leading-none font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-lg font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-sm leading-normal [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 gap-0 overflow-x-hidden overflow-y-auto rounded-2xl p-1 shadow-lg ring-1 duration-100;
@@ -553,11 +635,11 @@
@apply bg-border/50 -mx-1 my-1 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
@@ -582,7 +664,7 @@
/* Card */
.card {
@apply bg-card text-card-foreground ring-foreground/5 dark:ring-foreground/10 overflow-hidden rounded-[min(var(--radius-4xl),24px)] text-sm shadow-sm ring-1 has-[>img:first-child]:pt-0 [&>img:first-child]:rounded-t-[min(var(--radius-4xl),24px)] [&>img:last-child]:rounded-b-[min(var(--radius-4xl),24px)];
@apply bg-card text-card-foreground ring-foreground/5 dark:ring-foreground/10 overflow-hidden rounded-3xl text-sm shadow-sm ring-1 has-[>img:first-child]:pt-0 [&>img:first-child]:rounded-t-3xl [&>img:last-child]:rounded-b-3xl;
}
.card {
@@ -594,7 +676,7 @@
}
.card > header {
@apply gap-1.5 rounded-t-[min(var(--radius-4xl),24px)] px-5 [.border-b]:pb-5;
@apply gap-1.5 rounded-t-3xl px-5 [.border-b]:pb-5;
}
.card[data-size='sm'] > header {
@@ -618,7 +700,7 @@
}
.card > footer {
@apply rounded-b-[min(var(--radius-4xl),24px)] px-5 [.border-t]:pt-5;
@apply rounded-b-3xl px-5 [.border-t]:pt-5;
}
.card[data-size='sm'] > footer {
+91 -13
View File
@@ -1,5 +1,22 @@
/* Button */
/* Accordion */
@layer components {
.accordion > details {
@apply not-last:border-b;
}
.accordion > details > summary {
@apply focus-visible:ring-ring/30 focus-visible:border-ring rounded-none gap-6 py-4 text-left text-sm font-semibold hover:underline focus-visible:ring-2;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-3.5;
}
.accordion > details > :not(summary) {
@apply pt-0 pb-4 text-sm;
}
/* Button */
.btn {
@apply rounded-none border border-transparent bg-clip-padding text-xs font-semibold uppercase tracking-widest focus-visible:border-ring focus-visible:ring-ring/30 focus-visible:ring-[2px] aria-invalid:border-destructive aria-invalid:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-3.5;
}
@@ -263,7 +280,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -271,7 +288,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -317,17 +334,42 @@
@apply text-foreground underline-offset-4 hover:underline;
}
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-sm sm:gap-2.5;
}
.breadcrumb li {
@apply gap-1.5;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-none;
}
.command-dialog > * {
@apply rounded-none p-0;
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/10 rounded-none shadow-md ring-1 duration-100;
}
.command > header {
@apply m-1 gap-2 border-b border-b-input bg-transparent px-3;
@apply m-1 gap-0 border-b border-b-input bg-transparent px-3;
}
.command > header svg {
@@ -335,7 +377,7 @@
}
.command > header input {
@apply h-9 px-2 text-sm placeholder:text-muted-foreground;
@apply h-9 ps-1.5 pe-2 text-sm placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -363,7 +405,7 @@
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -386,11 +428,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 rounded-none p-1.5 shadow-md ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 gap-0 rounded-none p-0 shadow-md ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1.5 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1.5 overflow-y-auto p-1.5;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -407,7 +453,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -429,6 +475,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1.5 mb-0 h-8 w-auto border-transparent border-b-input bg-transparent px-2.5 shadow-none focus-within:border-transparent focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply border-transparent border-b-input bg-transparent focus-visible:border-b-ring aria-invalid:border-b-destructive dark:aria-invalid:border-b-destructive/50 h-10 data-[size=sm]:h-9 gap-1.5 rounded-none border px-0 py-2 text-sm transition-[color,border-color] [&_svg:not([class*='size-'])]:size-3.5;
@@ -481,10 +535,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/20 backdrop-blur-sm duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-xs gap-6 rounded-none p-6 shadow-md ring-1 duration-100 sm:max-w-md;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-[calc(100%-2rem)] gap-6 rounded-none p-6 text-sm shadow-md ring-1 duration-100 sm:max-w-md;
}
@@ -501,6 +560,15 @@
@apply gap-2;
}
.alert-dialog > * > header {
@apply gap-x-6 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-none [&>svg:not([class*='size-'])]:size-8;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -510,11 +578,21 @@
@apply text-lg leading-none font-semibold tracking-wider uppercase;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-lg font-semibold tracking-wider uppercase;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground mt-0.5 text-sm leading-relaxed [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground mt-0.5 text-sm leading-relaxed text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 min-w-48 gap-0 overflow-x-hidden overflow-y-auto rounded-none p-1.5 shadow-md ring-1 duration-100;
@@ -561,11 +639,11 @@
@apply bg-border/50 -mx-1.5 my-1.5 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+95 -17
View File
@@ -1,5 +1,22 @@
/* Button */
/* Accordion */
@layer components {
.accordion > details {
@apply not-last:border-b;
}
.accordion > details > summary {
@apply focus-visible:ring-ring/50 focus-visible:border-ring rounded-md py-4 text-left text-sm font-medium hover:underline focus-visible:ring-3;
}
.accordion > details > summary > svg:last-child {
@apply text-muted-foreground ms-auto size-4;
}
.accordion > details > :not(summary) {
@apply pt-0 pb-4 text-sm;
}
/* Button */
.btn {
@apply rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4;
}
@@ -247,7 +264,7 @@
@apply size-10;
}
.avatar > span:not([data-badge]) {
.avatar > span:not(.avatar-badge) {
@apply bg-muted text-muted-foreground rounded-[inherit];
}
@@ -255,7 +272,7 @@
@apply rounded-[inherit];
}
.avatar > [data-badge] {
.avatar > .avatar-badge {
@apply bg-primary text-primary-foreground ring-background;
}
@@ -301,17 +318,42 @@
@apply text-primary underline-offset-4 hover:underline;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-xl p-1;
/* Breadcrumb */
.breadcrumb > ol {
@apply text-muted-foreground gap-1.5 text-sm sm:gap-2.5;
}
.command-dialog > * {
@apply rounded-xl p-0;
.breadcrumb li {
@apply gap-1.5;
}
.breadcrumb a {
@apply transition-colors hover:text-foreground;
}
.breadcrumb [aria-current='page'] {
@apply font-normal text-foreground;
}
.breadcrumb li[aria-hidden='true'] > svg {
@apply size-3.5;
}
.breadcrumb li > span[aria-hidden='true'] {
@apply size-5 [&>svg]:size-4;
}
/* Command */
.command {
@apply bg-popover text-popover-foreground rounded-xl! p-1;
}
.command-dialog > .command {
@apply bg-popover text-popover-foreground ring-foreground/10 rounded-xl! ring-1 duration-100;
}
.command > header {
@apply m-1 mb-0 h-8 gap-2 rounded-lg border border-input/30 bg-input/30 px-2;
@apply m-1 mb-0 h-8 gap-0 rounded-lg border border-input/30 bg-input/30 px-2;
}
.command > header svg {
@@ -319,7 +361,7 @@
}
.command > header input {
@apply h-full text-sm placeholder:text-muted-foreground;
@apply ps-1.5 text-sm placeholder:text-muted-foreground;
}
.command [role='menu'] {
@@ -343,11 +385,11 @@
}
.command [role='menuitem'] {
@apply gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4;
@apply gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 in-[.command-dialog]:rounded-lg;
}
.command [role='menuitem'].active {
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
@apply bg-muted text-foreground [&_svg]:text-foreground;
}
.command [data-shortcut] {
@@ -370,11 +412,15 @@
}
.combobox [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 rounded-md p-1 shadow-md ring-1 duration-100;
@apply bg-popover text-popover-foreground ring-foreground/10 max-h-72 gap-0 rounded-md p-0 shadow-md ring-1 duration-100;
}
.combobox [role='listbox'] {
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto;
@apply max-h-[min(18rem,var(--available-height,18rem))] scroll-py-1 overflow-y-auto p-1;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true']))) {
@apply p-0;
}
.combobox [role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@@ -391,7 +437,7 @@
.combobox [role='option'].active,
.combobox [role='option']:focus-visible {
@apply bg-muted text-foreground [&_svg]:text-foreground;
@apply bg-accent text-accent-foreground [&_svg]:text-accent-foreground;
}
.combobox [role='group'] {
@@ -413,6 +459,14 @@
.combobox-chip-remove {
@apply -ms-1 opacity-50 hover:opacity-100 [&_svg]:size-3.5;
}
.combobox [data-popover] > .input-group {
@apply m-1 mb-0 h-8 w-auto border-input/30 bg-input/30 shadow-none focus-within:border-inherit focus-within:ring-0;
}
.combobox [data-popover] > input[role='combobox'] {
@apply mb-1 w-full;
}
/* Select */
*:not(select).select > button {
@apply border-input dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 data-[size=sm]:h-8 gap-1.5 rounded-md border bg-transparent py-2 ps-2.5 pe-2 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4;
@@ -465,10 +519,15 @@
/* Dialog */
.dialog::backdrop,
.alert-dialog::backdrop,
.command-dialog::backdrop {
@apply bg-black/10 backdrop-blur-xs duration-100;
}
.alert-dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-xs gap-6 rounded-xl p-6 ring-1 duration-100 sm:max-w-lg;
}
.dialog > * {
@apply bg-popover text-popover-foreground ring-foreground/10 max-w-[calc(100%-2rem)] gap-6 rounded-xl p-6 text-sm ring-1 duration-100 sm:max-w-md;
}
@@ -481,6 +540,15 @@
@apply gap-2;
}
.alert-dialog > * > header {
@apply gap-x-6 gap-y-1.5;
}
.alert-dialog > * > header > figure {
@apply bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-md [&>svg:not([class*='size-'])]:size-8;
}
.alert-dialog > * > footer,
.dialog > * > footer {
@apply gap-2;
}
@@ -490,11 +558,21 @@
@apply leading-none font-medium;
}
.alert-dialog > * > header > h2,
.alert-dialog > * > [data-title] {
@apply text-lg font-medium;
}
.dialog > * > header > p,
.dialog > * > [data-description] {
@apply text-muted-foreground text-sm leading-normal [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
.alert-dialog > * > header > p,
.alert-dialog > * > [data-description] {
@apply text-muted-foreground text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a:hover]:text-foreground;
}
/* Dropdown Menu */
.dropdown-menu > [data-popover] {
@apply bg-popover text-popover-foreground ring-foreground/10 gap-0 overflow-x-hidden overflow-y-auto rounded-md p-1 shadow-md ring-1 duration-100;
@@ -541,11 +619,11 @@
@apply bg-border -mx-1 my-1 h-px;
}
.dropdown-menu :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-muted-foreground font-sans text-xs tracking-widest;
}
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child) {
.dropdown-menu :is([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']):is(:focus-visible, .active) :is([data-shortcut], kbd:last-child, .kbd:last-child) {
@apply text-accent-foreground;
}
+26 -1
View File
@@ -8,6 +8,7 @@
multiple=false,
placeholder=None,
close_on_select=false,
clear=false,
auto_highlight=false,
format="value",
main_attrs={},
@@ -51,7 +52,23 @@
{% endfor %}
>
{% if multiple %}
<div class="combobox-chips {{ input_attrs.class }}">
<input
type="text"
role="combobox"
class="{{ input_attrs.class }}"
placeholder="{{ placeholder or '' }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
{% for key, value in input_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% elif clear %}
<div class="input-group {{ input_attrs.class }}">
<input
type="text"
role="combobox"
@@ -66,6 +83,14 @@
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<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="{{ id }}-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>
{% else %}
<input
+2 -2
View File
@@ -50,7 +50,7 @@
id="{{ id }}-menu"
aria-orientation="vertical"
data-empty="{{ empty_text }}"
class="scrollbar-sm {{ menu_attrs.class }}"
class="{{ menu_attrs.class }}"
{% for key, value in menu_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
@@ -121,7 +121,7 @@
id="{{ id }}-menu"
aria-orientation="vertical"
data-empty="{{ empty_text }}"
class="scrollbar-sm {{ menu_attrs.class }}"
class="{{ menu_attrs.class }}"
{% for key, value in menu_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
+68
View File
@@ -0,0 +1,68 @@
(() => {
const states = new WeakMap();
const isDisabled = (details) => {
const summary = details.querySelector(':scope > summary');
return details.getAttribute('aria-disabled') === 'true'
|| details.dataset.disabled === 'true'
|| summary?.getAttribute('aria-disabled') === 'true';
};
const isMultiple = (root) => root.hasAttribute('data-multiple');
const closeSiblings = (root, activeDetails) => {
if (isMultiple(root) || !activeDetails.open) return;
root.querySelectorAll(':scope > details[open]').forEach((details) => {
if (details !== activeDetails) details.open = false;
});
};
const initAccordion = (root) => {
if (root.dataset.accordionInitialized) return;
const handleClick = (event) => {
const summary = event.target.closest('summary');
const details = summary?.closest('details');
if (!details || details.parentElement !== root || !isDisabled(details)) return;
event.preventDefault();
};
const handleKeydown = (event) => {
if (event.key !== 'Enter' && event.key !== ' ') return;
const summary = event.target.closest('summary');
const details = summary?.closest('details');
if (!details || details.parentElement !== root || !isDisabled(details)) return;
event.preventDefault();
};
const handleToggle = (event) => {
const details = event.target;
if (details.parentElement !== root) return;
if (isDisabled(details)) {
details.open = false;
return;
}
closeSiblings(root, details);
};
root.addEventListener('click', handleClick);
root.addEventListener('keydown', handleKeydown);
root.addEventListener('toggle', handleToggle, true);
root.querySelectorAll(':scope > details[open]').forEach((details) => closeSiblings(root, details));
states.set(root, { handleClick, handleToggle });
root._destroy = () => {
root.removeEventListener('click', handleClick);
root.removeEventListener('keydown', handleKeydown);
root.removeEventListener('toggle', handleToggle, true);
states.delete(root);
};
root.dataset.accordionInitialized = 'true';
root.dispatchEvent(new CustomEvent('basecoat:initialized'));
};
if (window.basecoat) {
window.basecoat.register('accordion', '.accordion:not([data-accordion-initialized])', initAccordion);
}
})();
+115 -26
View File
@@ -2,12 +2,29 @@
const states = new WeakMap();
const getElements = (root) => {
const input = root.querySelector(':scope > input[role="combobox"], :scope > .combobox-chips input[role="combobox"]');
const chips = root.querySelector(':scope > .combobox-chips');
const popover = root.querySelector(':scope > [data-popover]');
const input = root.querySelector(':scope > input[role="combobox"], :scope > .input-group input[role="combobox"], :scope > .combobox-chips input[role="combobox"]')
|| popover?.querySelector('input[role="combobox"]');
const chips = root.querySelector(':scope > .combobox-chips');
const popupTrigger = root.querySelector(':scope > button[aria-haspopup="listbox"]');
const trigger = popupTrigger || root.querySelector(':scope > .input-group button[aria-haspopup="listbox"]');
const clearButton = root.querySelector('[data-clear]');
const valueTarget = popupTrigger?.querySelector('[data-value]') || (popupTrigger?.matches('[data-value]') ? popupTrigger : null);
const listbox = popover ? popover.querySelector('[role="listbox"]') : null;
const hiddenInput = root.querySelector(':scope > input[type="hidden"]');
return { input, chips, popover, listbox, hiddenInput };
return { input, chips, trigger, clearButton, valueTarget, popover, listbox, hiddenInput };
};
const ensureMultipleInputSurface = (root, elements) => {
if (elements.listbox?.getAttribute('aria-multiselectable') !== 'true' || elements.chips || !elements.input) return elements;
if (elements.input.parentElement !== root) return elements;
const chips = document.createElement('div');
chips.className = 'combobox-chips';
root.insertBefore(chips, elements.input);
chips.appendChild(elements.input);
return getElements(root);
};
const getValue = option => option.dataset.value ?? option.textContent.trim();
@@ -111,7 +128,7 @@
state.popover.dataset.empty = String(state.visibleOptions.length === 0);
};
const filterOptions = (state, { preserveActive = false } = {}) => {
const filterOptions = (state, { preserveActive = false, search: forcedSearch } = {}) => {
const previousActive = state.activeIndex > -1 ? state.options[state.activeIndex] : null;
state.visibleOptions = [];
@@ -127,7 +144,7 @@
return;
}
const search = state.input.value.trim().toLowerCase();
const search = (forcedSearch ?? state.input.value).trim().toLowerCase();
state.allOptions.forEach(option => {
if (option.hasAttribute('data-force')) {
@@ -161,20 +178,42 @@
const chip = document.createElement('span');
chip.className = 'combobox-chip';
chip.dataset.value = entry.value;
chip.textContent = entry.label;
const label = document.createElement('span');
label.textContent = entry.label;
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'combobox-chip-remove';
remove.className = 'combobox-chip-remove btn';
remove.dataset.variant = 'ghost';
remove.dataset.size = 'icon-xs';
remove.setAttribute('aria-label', `Remove ${entry.label}`);
remove.innerHTML = '<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"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
remove.addEventListener('click', () => root.deselect(entry.value));
remove.addEventListener('click', (event) => {
event.stopPropagation();
root.deselect(entry.value);
state.input.focus();
});
chip.appendChild(label);
chip.appendChild(remove);
state.chips.insertBefore(chip, state.input);
});
};
const syncTriggerValue = (state) => {
if (!state.valueTarget) return;
const selected = getSelection(state);
state.valueTarget.textContent = state.isMultiple
? selected.map(entry => entry.label).join(', ')
: (selected[0]?.label || state.valueTarget.dataset.placeholder || '');
};
const syncClearButton = (state) => {
if (!state.clearButton) return;
state.clearButton.hidden = getSelection(state).length === 0 && state.input.value === '';
};
const syncSelectedOptions = (state) => {
state.options.forEach(option => {
if (state.selected.has(getValue(option))) {
@@ -195,7 +234,7 @@
state.input.value = '';
} else if (normalized[0]) {
state.selected.set(normalized[0].value, normalized[0]);
state.input.value = normalized[0].label;
state.input.value = state.valueTarget ? '' : normalized[0].label;
} else {
state.input.value = '';
}
@@ -206,6 +245,8 @@
renderChips(root);
filterOptions(state, { preserveActive: true });
}
syncTriggerValue(state);
syncClearButton(state);
if (triggerEvent) {
root.dispatchEvent(new CustomEvent('change', {
@@ -219,15 +260,35 @@
if (state.popover.getAttribute('aria-hidden') === 'true') return;
state.popover.setAttribute('aria-hidden', 'true');
state.input.setAttribute('aria-expanded', 'false');
state.trigger?.setAttribute('aria-expanded', 'false');
setActiveOption(state, -1);
if (focusInput) state.input.focus();
if (focusInput) {
state.skipOpenOnFocus = true;
state.input.focus();
requestAnimationFrame(() => { state.skipOpenOnFocus = false; });
}
};
const openPopover = (state) => {
const { root } = state;
if (state.popover.getAttribute('aria-hidden') === 'false') return;
document.dispatchEvent(new CustomEvent('basecoat:popover', { detail: { source: root } }));
root.refresh();
const selected = getSelection(state)[0];
if (state.valueTarget) state.input.value = '';
const search = !state.isMultiple && selected && state.input.value === selected.label ? '' : undefined;
filterOptions(state, { search });
state.popover.setAttribute('aria-hidden', 'false');
state.input.setAttribute('aria-expanded', 'true');
state.trigger?.setAttribute('aria-expanded', 'true');
if (state.trigger) state.input.focus();
};
const refreshCombobox = (root) => {
const state = states.get(root);
if (!state) return;
const elements = getElements(root);
let elements = getElements(root);
if (!elements.input || !elements.popover || !elements.listbox || !elements.hiddenInput) {
const missing = [];
if (!elements.input) missing.push('input');
@@ -238,6 +299,7 @@
return;
}
elements = ensureMultipleInputSurface(root, elements);
const previousValue = elements.hiddenInput.value;
const previousInputValue = elements.input.value;
Object.assign(state, elements, getOptions(elements.listbox));
@@ -264,6 +326,8 @@
filterOptions(state, { preserveActive: true });
}
}
syncTriggerValue(state);
syncClearButton(state);
};
const resolveEntry = (state, value) => {
@@ -290,7 +354,8 @@
if (state.closeOnSelect) root.close(true);
} else {
setSelected(root, entry);
root.close(true);
root.close(state.trigger ? false : true);
state.trigger?.focus();
}
};
@@ -302,6 +367,15 @@
setSelected(root, getSelection(state).filter(item => item.value !== normalized));
};
const clearValue = (root) => {
const state = states.get(root);
setSelected(root, [], true);
state.input.value = '';
filterOptions(state);
syncClearButton(state);
if (state.popover.getAttribute('aria-hidden') === 'false') state.input.focus();
};
const handleKeydown = (event, root) => {
const state = states.get(root);
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End', 'Escape', 'Backspace'].includes(event.key)) return;
@@ -322,7 +396,7 @@
if (!isOpen && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
event.preventDefault();
root.open();
openPopover(state);
}
if (state.popover.getAttribute('aria-hidden') === 'true') return;
@@ -355,7 +429,7 @@
const initCombobox = (root) => {
if (root.dataset.comboboxInitialized) return;
const state = { root, activeIndex: -1, allOptions: [], options: [], visibleOptions: [], selected: new Map(), format: 'value', manualFilter: false };
const state = { root, activeIndex: -1, allOptions: [], options: [], visibleOptions: [], selected: new Map(), format: 'value', manualFilter: false, skipOpenOnFocus: false };
states.set(root, state);
root.refresh = () => refreshCombobox(root);
@@ -366,14 +440,6 @@
return;
}
root.open = () => {
if (state.popover.getAttribute('aria-hidden') === 'false') return;
document.dispatchEvent(new CustomEvent('basecoat:popover', { detail: { source: root } }));
root.refresh();
filterOptions(state);
state.popover.setAttribute('aria-hidden', 'false');
state.input.setAttribute('aria-expanded', 'true');
};
root.close = (focusInput = false) => closePopover(state, focusInput);
root.select = (value) => selectValue(root, value);
@@ -394,17 +460,34 @@
root.selectNone = () => setSelected(root, []);
}
const handleInputFocus = root.open;
const handleInputClick = root.open;
const handleInputFocus = () => {
if (state.skipOpenOnFocus) return;
openPopover(state);
};
const handleInputClick = () => openPopover(state);
const handleInput = () => {
root.open();
openPopover(state);
filterOptions(state);
if (!state.isMultiple) {
state.hiddenInput.value = '';
state.selected.clear();
syncSelectedOptions(state);
syncTriggerValue(state);
syncClearButton(state);
}
};
const handleTriggerClick = () => {
if (state.popover.getAttribute('aria-hidden') === 'false') {
root.close(false);
} else {
openPopover(state);
}
};
const handleClearClick = (event) => {
event.preventDefault();
event.stopPropagation();
clearValue(root);
};
const handleInputKeydown = (event) => handleKeydown(event, root);
const handleListboxMousemove = (event) => {
const option = event.target.closest('[role="option"]');
@@ -427,6 +510,8 @@
state.input.addEventListener('click', handleInputClick);
state.input.addEventListener('input', handleInput);
state.input.addEventListener('keydown', handleInputKeydown);
state.trigger?.addEventListener('click', handleTriggerClick);
state.clearButton?.addEventListener('click', handleClearClick);
state.listbox.addEventListener('mousemove', handleListboxMousemove);
state.listbox.addEventListener('click', handleListboxClick);
document.addEventListener('click', handleDocumentClick);
@@ -437,6 +522,8 @@
state.input.removeEventListener('click', handleInputClick);
state.input.removeEventListener('input', handleInput);
state.input.removeEventListener('keydown', handleInputKeydown);
state.trigger?.removeEventListener('click', handleTriggerClick);
state.clearButton?.removeEventListener('click', handleClearClick);
state.listbox.removeEventListener('mousemove', handleListboxMousemove);
state.listbox.removeEventListener('click', handleListboxClick);
document.removeEventListener('click', handleDocumentClick);
@@ -444,11 +531,11 @@
state.chips?.querySelectorAll('.combobox-chip').forEach(chip => chip.remove());
states.delete(root);
delete root.refresh;
delete root.open;
delete root.close;
delete root.select;
delete root.selectByValue;
delete root.setValue;
delete root.clear;
delete root.deselect;
delete root.toggle;
delete root.selectAll;
@@ -457,6 +544,8 @@
state.popover.setAttribute('aria-hidden', 'true');
state.input.setAttribute('aria-expanded', 'false');
state.trigger?.setAttribute('aria-expanded', 'false');
root.clear = () => clearValue(root);
Object.defineProperty(root, 'value', {
configurable: true,
+3 -1
View File
@@ -2,7 +2,9 @@
const states = new WeakMap();
const isDisabled = (item) =>
item.hasAttribute('disabled') || item.getAttribute('aria-disabled') === 'true';
item.hasAttribute('disabled') ||
item.getAttribute('aria-disabled') === 'true' ||
item.getAttribute('data-disabled') === 'true';
const getElements = (container) => ({
input: container.querySelector('header input'),
+12 -1
View File
@@ -146,7 +146,18 @@
const handleMenuMouseleave = () => setActiveItem(state, -1);
const handleMenuClick = (event) => {
const menuItem = event.target.closest('[role^="menuitem"]');
if (menuItem && !isDisabled(menuItem)) root.close();
if (!menuItem || isDisabled(menuItem)) return;
if (menuItem.getAttribute('role') === 'menuitemcheckbox') {
menuItem.setAttribute('aria-checked', menuItem.getAttribute('aria-checked') !== 'true');
} else if (menuItem.getAttribute('role') === 'menuitemradio') {
const group = menuItem.closest('[role="group"], [role="menu"]');
group?.querySelectorAll('[role="menuitemradio"]').forEach((item) => {
if (!isDisabled(item)) item.setAttribute('aria-checked', item === menuItem ? 'true' : 'false');
});
}
root.close();
};
const handleDocumentClick = (event) => {
+27 -1
View File
@@ -7,6 +7,7 @@
@param multiple {boolean} [optional] [default=false] - Enables multiple selection mode with chips.
@param placeholder {string} [optional] - Placeholder text for the visible input.
@param close_on_select {boolean} [optional] [default=false] - Closes the popover when selecting an option in multiple mode.
@param clear {boolean} [optional] [default=false] - Shows a clear button in single-select mode.
@param auto_highlight {boolean} [optional] [default=false] - Highlights the first matching option while filtering.
@param format {string} [optional] [default="value"] - Hidden input serialization format: "value" or "object".
@param main_attrs {object} [optional] - Additional HTML attributes for the root div.
@@ -22,6 +23,7 @@
multiple=false,
placeholder=None,
close_on_select=false,
clear=false,
auto_highlight=false,
format="value",
main_attrs={},
@@ -65,7 +67,23 @@
{% endfor %}
>
{% if multiple %}
<div class="combobox-chips {{ input_attrs.class }}">
<input
type="text"
role="combobox"
class="{{ input_attrs.class }}"
placeholder="{{ placeholder or '' }}"
autocomplete="off"
autocorrect="off"
spellcheck="false"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
{% for key, value in input_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% elif clear %}
<div class="input-group {{ input_attrs.class }}">
<input
type="text"
role="combobox"
@@ -80,6 +98,14 @@
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<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="{{ id }}-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>
{% else %}
<input
+2 -2
View File
@@ -50,7 +50,7 @@
id="{{ id }}-menu"
aria-orientation="vertical"
data-empty="{{ empty_text }}"
class="scrollbar-sm {{ menu_attrs.class }}"
class="{{ menu_attrs.class }}"
{% for key, value in menu_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
@@ -121,7 +121,7 @@
id="{{ id }}-menu"
aria-orientation="vertical"
data-empty="{{ empty_text }}"
class="scrollbar-sm {{ menu_attrs.class }}"
class="{{ menu_attrs.class }}"
{% for key, value in menu_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
+50 -48
View File
@@ -1,8 +1,7 @@
---
import "../css/landing.css";
import basecoatJsUrl from "basecoat-css/basecoat.min?url";
import dropdownMenuJsUrl from "basecoat-css/dropdown-menu.min?url";
import { CommandDialog } from "reallysimpledocs/components";
import basecoatJsUrl from "basecoat-css/all.min?url";
import StyleHead from "../../docs/src/site/StyleHead.astro";
import vegaUrl from "../../docs/src/assets/styles/style-vega.css?url";
import novaUrl from "../../docs/src/assets/styles/style-nova.css?url";
import maiaUrl from "../../docs/src/assets/styles/style-maia.css?url";
@@ -180,36 +179,7 @@ const sidebarSections = [
});
})();
</script>
<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 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 nextStyle = Object.prototype.hasOwnProperty.call(styleUrls, getStoredStyle()) ? getStoredStyle() : "vega";
const href = getStyleHref(nextStyle);
document.documentElement.dataset.styleVariant = nextStyle;
if (!href) return;
document.documentElement.setAttribute("data-style-pending", "");
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()">`,
);
})();
</script>
<StyleHead />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title} | Basecoat</title>
@@ -224,9 +194,9 @@ const sidebarSections = [
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={socialImage.src} />
<script src={basecoatJsUrl} defer></script>
<script src={dropdownMenuJsUrl} defer></script>
</head>
<body class="min-h-dvh overflow-x-clip bg-background text-foreground antialiased">
<CommandDialog />
<header class="bg-background sticky inset-x-0 top-0 isolate z-30 flex shrink-0 items-center gap-2">
<div class="flex h-14 w-full items-center justify-between gap-2 px-4">
<div class="flex min-w-0 items-center gap-1">
@@ -242,6 +212,18 @@ const sidebarSections = [
</div>
<div class="ml-auto flex min-w-0 flex-1 items-center justify-end gap-2">
<div
class="input-group h-8 w-full min-w-0 max-w-72 cursor-text sm:ml-auto"
role="button"
tabindex="0"
aria-label="Search docs"
onclick="window.rsdOpenCommandSearch?.()"
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.rsdOpenCommandSearch?.(); }"
>
<input type="text" placeholder="Search..." readonly tabindex="-1" aria-hidden="true" />
<span data-align="start" aria-hidden="true"><Fragment set:html={Search} /></span>
<span data-align="end" aria-hidden="true"><kbd class="kbd">⌘K</kbd></span>
</div>
<select id="style-variant-select" class="select hidden h-8 w-28 shrink-0 text-sm leading-none sm:block" aria-label="Style pack">
<option value="vega">Vega</option>
<option value="nova">Nova</option>
@@ -290,7 +272,7 @@ const sidebarSections = [
<section class="flex-1 p-0">
<div class="w-full overflow-hidden">
<div class="theme-neutral relative flex h-[980px] max-h-[calc(100svh-14rem)] min-h-[680px] w-full max-w-none flex-col gap-(--gap) overflow-hidden bg-muted p-12 pb-0! [--gap:--spacing(8)] min-[1900px]:h-[1120px] min-[1900px]:p-12 min-[1900px]:[--gap:--spacing(10)]! lg:p-6 lg:[--gap:--spacing(6)] dark:bg-background">
<div class="relative flex h-[980px] max-h-[calc(100svh-14rem)] min-h-[680px] w-full max-w-none flex-col gap-(--gap) overflow-hidden bg-muted p-12 pb-0! [--gap:--spacing(8)] min-[1900px]:h-[1120px] min-[1900px]:p-12 min-[1900px]:[--gap:--spacing(10)]! lg:p-6 lg:[--gap:--spacing(6)] dark:bg-background" style="--chart-1: oklch(0.87 0 0); --chart-2: oklch(0.556 0 0); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2);">
<div class="relative z-10 mx-auto grid w-full grid-cols-1 gap-(--gap) [&_.card]:w-full md:max-w-3xl md:grid-cols-2 lg:max-w-none lg:grid-cols-3 xl:max-w-[1600px] min-[1400px]:grid-cols-4! 2xl:max-w-[1900px] min-[1900px]:grid-cols-5!">
<div class="flex min-w-0 flex-col gap-(--gap)">
<article class="card relative">
@@ -392,7 +374,7 @@ const sidebarSections = [
["Retirement", "$420,000", "65% achieved", "$273,000", "65%"],
["Real Estate", "$85,000", "32% achieved", "$27,200", "32%"],
].map(([label, amount, progress, value, width]) => (
<div class="landing-demo-surface bg-muted/50 p-4">
<div class="rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none bg-muted/50 p-4">
<p class="text-xs font-medium tracking-wider text-muted-foreground uppercase">{label}</p>
<p class="mt-3 text-3xl font-semibold tabular-nums">{amount}</p>
<div class="progress mt-3" role="progressbar" aria-label={`${label} savings progress`} aria-valuenow={parseInt(width, 10)} aria-valuemin="0" aria-valuemax="100"><span style={`width:${width}`}></span></div>
@@ -418,7 +400,7 @@ const sidebarSections = [
["May", 100],
].map(([month, height]) => (
<div class="flex h-full flex-1 flex-col justify-end gap-2">
<div class="landing-demo-chart-bar min-h-2 bg-chart-2" style={`height:${height}%`}></div>
<div class="rounded-t-md [html[data-style-variant=lyra]_&]:rounded-t-none [html[data-style-variant=sera]_&]:rounded-t-none min-h-2 bg-chart-2" style={`height:${height}%`}></div>
<span class="text-center text-xs text-muted-foreground">{month}</span>
</div>
))}
@@ -426,8 +408,8 @@ const sidebarSections = [
</section>
<section>
<div class="grid w-full grid-cols-1 gap-3 xl:grid-cols-2">
<div class="landing-demo-surface bg-muted/50 p-4"><p class="text-xs font-medium tracking-wider text-muted-foreground uppercase">Upcoming</p><p class="text-base font-semibold">May 2024</p><p class="text-sm text-muted-foreground">Scheduled</p></div>
<div class="landing-demo-surface hidden bg-muted/50 p-4 xl:block"><p class="text-xs font-medium tracking-wider text-muted-foreground uppercase">Savings Plan</p><p class="text-base font-semibold">Accelerated</p><p class="text-sm text-muted-foreground">Recurring</p></div>
<div class="rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none bg-muted/50 p-4"><p class="text-xs font-medium tracking-wider text-muted-foreground uppercase">Upcoming</p><p class="text-base font-semibold">May 2024</p><p class="text-sm text-muted-foreground">Scheduled</p></div>
<div class="rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none hidden bg-muted/50 p-4 xl:block"><p class="text-xs font-medium tracking-wider text-muted-foreground uppercase">Savings Plan</p><p class="text-base font-semibold">Accelerated</p><p class="text-sm text-muted-foreground">Recurring</p></div>
</div>
</section>
<footer><button class="btn w-full" type="button">View Full Report</button></footer>
@@ -440,7 +422,7 @@ const sidebarSections = [
<span class="badge" data-variant="outline"><span class="size-2 rounded-full bg-yellow-500"></span> Pending Setup</span>
</header>
<section class="flex flex-1 flex-col justify-end">
<article class="item landing-demo-surface" data-variant="muted">
<article class="item rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none" data-variant="muted">
<section class="gap-3">
<div class="flex justify-between"><span class="text-muted-foreground">Net Royalties</span><span class="font-medium tabular-nums">$1,248.75</span></div>
<div class="mt-3 flex justify-between"><span class="text-muted-foreground">Processing Fee</span><span class="font-medium tabular-nums">-$37.46</span></div>
@@ -460,9 +442,9 @@ const sidebarSections = [
<section>
<div class="grid gap-2">
{holdings.map(([name, shares, _amount, data]) => (
<div class="landing-demo-surface flex items-center gap-3 bg-muted/50 p-3">
<div class="rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none flex items-center gap-3 bg-muted/50 p-3">
<div class="mr-auto"><p class="text-sm font-medium">{name}</p><p class="text-sm text-muted-foreground">{shares}</p></div>
<div class="hidden h-8 w-24 items-end gap-1 md:flex">{data.map((height) => <span class="landing-demo-chart-bar min-h-1 flex-1 bg-chart-2" style={`height:${height}%`}></span>)}</div>
<div class="hidden h-8 w-24 items-end gap-1 md:flex">{data.map((height) => <span class="rounded-t-md [html[data-style-variant=lyra]_&]:rounded-t-none [html[data-style-variant=sera]_&]:rounded-t-none min-h-1 flex-1 bg-chart-2" style={`height:${height}%`}></span>)}</div>
</div>
))}
</div>
@@ -499,14 +481,14 @@ const sidebarSections = [
<div class="field"><label for="email-address">Email Address</label><input id="email-address" class="input" type="email" placeholder="artist@studio.inc" /></div>
<div class="field"><div class="flex items-center justify-between"><label for="current-password">Current Password</label><a href="#" class="text-xs font-medium tracking-wider text-muted-foreground uppercase hover:text-foreground">Forgot?</a></div><input id="current-password" class="input" type="password" placeholder="••••••••••••••••••••••••" /></div>
</section>
<footer class="flex-col gap-4"><button class="btn w-full" type="button"><Fragment set:html={Lock} /> Update Security</button><a href="#" class="item landing-demo-surface" data-variant="muted"><figure class="text-destructive"><Fragment set:html={Info} /></figure><section><h3>Danger Zone</h3><p class="line-clamp-1">Archive account and remove catalog</p></section><aside class="text-muted-foreground [&_svg]:size-4"><Fragment set:html={ChevronRight} /></aside></a></footer>
<footer class="flex-col gap-4"><button class="btn w-full" type="button"><Fragment set:html={Lock} /> Update Security</button><a href="#" class="item rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none" data-variant="muted"><figure class="text-destructive"><Fragment set:html={Info} /></figure><section><h3>Danger Zone</h3><p class="line-clamp-1">Archive account and remove catalog</p></section><aside class="text-muted-foreground [&_svg]:size-4"><Fragment set:html={ChevronRight} /></aside></a></footer>
</article>
</div>
<div class="hidden min-w-0 flex-col gap-(--gap) md:flex">
<article class="card relative">
<section class="flex justify-center pt-6">
<div class="landing-demo-surface border border-border bg-white p-4">
<div class="rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none border border-border bg-white p-4">
<svg viewBox="0 0 21 21" class="size-40 text-black" role="img" aria-label="Connect device QR code" shape-rendering="crispEdges">
<rect width="21" height="21" fill="white" />
{qrRows.map((row, y) => [...row].map((cell, x) => cell === "1" && <rect x={x} y={y} width="1" height="1" />))}
@@ -522,7 +504,7 @@ const sidebarSections = [
<div class="field w-full"><label for="transfer-amount">Amount to Transfer</label><div class="input-group w-full"><span data-align="start">$</span><input id="transfer-amount" class="w-full" value="1,200.00" /></div></div>
<div class="field w-full"><label for="from-account">From Account</label><select id="from-account" class="select w-full"><option>Main Checking (··8402) — $12,450.00</option></select></div>
<div class="field w-full"><label for="to-account">To Account</label><select id="to-account" class="select w-full"><option>High Yield Savings (··1192) — $42,100.00</option></select></div>
<article class="item landing-demo-surface" data-variant="muted">
<article class="item rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none" data-variant="muted">
<section class="gap-3">
<div class="flex justify-between"><span class="text-muted-foreground">Estimated arrival</span><span class="font-medium">Today, Apr 14</span></div><hr class="my-3 border-border" />
<div class="flex justify-between"><span class="text-muted-foreground">Transaction fee</span><span class="font-medium tabular-nums">$0.00</span></div><hr class="my-3 border-border" />
@@ -573,7 +555,7 @@ const sidebarSections = [
["Scheduled transfers", "Set up a transfer to send at a later date."],
["Recurring card payments", "Manage your repeated card transactions."],
].map(([name, desc], index) => (
<a href="#" class="item landing-demo-surface" data-variant="muted">
<a href="#" class="item rounded-md [html[data-style-variant=nova]_&]:rounded-lg [html[data-style-variant=maia]_&]:rounded-2xl [html[data-style-variant=luma]_&]:rounded-2xl [html[data-style-variant=rhea]_&]:rounded-2xl [html[data-style-variant=lyra]_&]:rounded-none [html[data-style-variant=sera]_&]:rounded-none" data-variant="muted">
<figure><Fragment set:html={index === 0 ? Settings : index === 1 ? Calendar : Refresh} /></figure>
<section><h3>{name}</h3><p>{desc}</p></section>
<aside class="text-muted-foreground [&_svg]:size-4"><Fragment set:html={ChevronRight} /></aside>
@@ -630,7 +612,7 @@ const sidebarSections = [
<section class="flex flex-col gap-4">
<div class="flex h-[140px] w-full items-end gap-2" role="img" aria-label="Power usage by hour">
{[32, 74, 82, 63, 89, 76, 100, 84].map((height, index) => (
<div class="flex h-full flex-1 flex-col justify-end gap-1.5"><div class="landing-demo-chart-bar min-h-2 bg-chart-2" style={`height:${height}%`}></div><span class="text-center text-xs text-muted-foreground">{["6a", "8a", "10a", "12p", "2p", "4p", "6p", "8p"][index]}</span></div>
<div class="flex h-full flex-1 flex-col justify-end gap-1.5"><div class="rounded-t-md [html[data-style-variant=lyra]_&]:rounded-t-none [html[data-style-variant=sera]_&]:rounded-t-none min-h-2 bg-chart-2" style={`height:${height}%`}></div><span class="text-center text-xs text-muted-foreground">{["6a", "8a", "10a", "12p", "2p", "4p", "6p", "8p"][index]}</span></div>
))}
</div>
<hr class="border-border" />
@@ -649,6 +631,26 @@ const sidebarSections = [
</section>
</main>
<footer class="px-6 py-8 text-center text-sm text-muted-foreground">
Built by
<a
href="https://x.com/hunvreus"
target="_blank"
rel="noopener"
class="underline underline-offset-2 decoration-muted-foreground/50 transition-all hover:decoration-foreground/50"
>
Ronan Berder</a
>.
<a
href="https://github.com/sponsors/hunvreus"
target="_blank"
rel="noopener"
class="underline underline-offset-2 decoration-muted-foreground/50 transition-all hover:decoration-foreground/50"
>
Sponsor me</a
>.
</footer>
<script is:inline define:vars={{ styleUrls }}>
(() => {
const styleSelect = document.getElementById("style-variant-select");