13 / 17
All 17 in 17 Tailwind CSS Dashboard Layouts
- 01Tailwind CSS Bento Grid Dashboard Layout
- 02Tailwind CSS Collapsible Sidebar Admin Dashboard
- 03Tailwind CSS Dashboard KPI Stat Cards Grid
- 04Tailwind CSS Master-Detail Split View Inbox Layout
- 05Tailwind CSS Fixed Header Dashboard with Independent Scroll Panels
- 06Tailwind CSS Grid Template Areas Dashboard Layout
- 07Tailwind CSS Responsive Off-Canvas Sidebar Drawer Dashboard
- 08Tailwind CSS Container Query Responsive Dashboard Cards
- 09Tailwind CSS E-commerce Admin Dashboard with Bulk Actions
- 10Tailwind CSS Dark Mode Dashboard with Light / Dark / System Toggle
- 11Tailwind CSS Multi-Level Nested Sidebar Navigation Layout
- 12Minimal Tailwind CSS Admin Dashboard Template (No JavaScript)
- 13Tailwind CSS AI Copilot Chat Sidebar Dashboard Layout
- 14Tailwind CSS Data Table with Sticky Header and Sticky First Column
- 15Tailwind CSS Masonry Grid Dashboard Layout (Columns vs Grid Dense)
- 16Tailwind CSS Full-Screen Analytics Dashboard (100vh, No Scroll)
- 17Tailwind CSS Top Navigation Bar Dashboard Layout with Tabs
Tailwind CSS AI Copilot Chat Sidebar Dashboard Layout
Your data on the left, a resident AI copilot docked on the right. The panel folds by animating grid-cols-[minmax(0,1fr)_21.25rem] to group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem], so the dashboard reflows in the same motion. New in the port: the copilot actually talks — the pending answer streams in word by word, suggestion chips and the composer send real messages, and Esc closes the panel.
Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69
Cruip · Mosaic by Cruip · Affiliate link
The code
<section class="tdl-13 @container/s w-full min-h-svh bg-tdl13-bg font-tdl13-sans text-tdl13-ink antialiased" aria-label="AI copilot split dashboard demo">
<div data-split class="group/split grid h-svh min-h-[34rem] grid-cols-[minmax(0,1fr)_21.25rem] overflow-hidden transition-[grid-template-columns] duration-350 ease-[cubic-bezier(.2,.7,.2,1)] group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem] @max-[44rem]/s:grid-cols-[minmax(0,1fr)_17.5rem]">
<div class="flex min-w-0 flex-col">
<header class="flex h-15 flex-none items-center justify-between gap-3 border-b border-tdl13-edge bg-tdl13-panel px-4">
<strong class="flex min-w-0 items-center gap-2.5 truncate text-[15px] font-semibold"><span aria-hidden="true" class="size-5 flex-none rounded-md bg-[conic-gradient(from_210deg,var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))]"></span>Margin — Q3 board</strong>
<button data-ask type="button" aria-expanded="true" aria-controls="tdl13-panel" class="flex h-10 flex-none items-center gap-2 rounded-full border border-tdl13-ai/40 bg-linear-120 from-tdl13-ai/10 to-tdl13-ai2/10 px-4 text-[13px] font-semibold text-tdl13-aiink transition hover:-translate-y-px hover:shadow-[0_6px_18px_-8px_var(--color-tdl13-ai)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><i aria-hidden="true" class="size-3.5 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))] animate-tdl13-spin"></i>Copilot<kbd class="rounded border border-tdl13-ai/30 px-1 font-sans text-[10px] text-tdl13-aiink/80 @max-[44rem]/s:hidden">⌘J</kbd></button>
</header>
<div class="grid min-h-0 flex-1 auto-rows-min grid-cols-[repeat(auto-fit,minmax(min(10.5rem,100%),1fr))] gap-3 overflow-y-auto overscroll-contain p-4 [&>article]:rounded-2xl [&>article]:border [&>article]:border-tdl13-edge [&>article]:bg-tdl13-panel [&>article]:px-4.5 [&>article]:py-4 [&_h3]:text-[10.5px] [&_h3]:font-bold [&_h3]:tracking-[0.1em] [&_h3]:text-tdl13-mut [&_h3]:uppercase">
<article><h3>Gross margin</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">61.4%</p><small class="text-[11.5px] font-semibold text-tdl13-good">▲ 2.1 pts QoQ</small></article>
<article><h3>Burn multiple</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">1.3×</p><small class="text-[11.5px] font-semibold text-tdl13-good">improving</small></article>
<article><h3>Net revenue retention</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">117%</p><small class="text-[11.5px] font-semibold text-tdl13-mut">flat QoQ</small></article>
<article class="col-span-full"><h3 class="flex flex-wrap items-center justify-between gap-2">Revenue vs. cost<span class="flex gap-3 tracking-normal normal-case"><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai"></i>Revenue</span><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai2/55"></i>Cost</span></span></h3>
<div aria-hidden="true" class="mt-3 flex h-28 items-end gap-2.25 *:relative *:h-full *:flex-1 *:before:absolute *:before:bottom-0 *:before:left-0 *:before:h-(--a) *:before:w-[52%] *:before:rounded-t-[3px] *:before:bg-tdl13-ai *:before:content-[''] *:after:absolute *:after:right-0 *:after:bottom-0 *:after:h-(--b) *:after:w-[34%] *:after:rounded-t-[3px] *:after:bg-tdl13-ai2/55 *:after:content-['']"><i style="--a:46%;--b:30%"></i><i style="--a:52%;--b:33%"></i><i style="--a:49%;--b:35%"></i><i style="--a:61%;--b:36%"></i><i style="--a:66%;--b:38%"></i><i style="--a:74%;--b:39%"></i><i style="--a:82%;--b:41%"></i><i style="--a:90%;--b:42%"></i></div>
</article>
</div>
</div>
<aside id="tdl13-panel" aria-label="AI copilot" class="min-w-0 overflow-hidden border-l border-tdl13-edge bg-tdl13-panel transition-[visibility] group-data-closed/split:invisible group-data-closed/split:delay-350">
<div class="flex h-full w-85 flex-col @max-[44rem]/s:w-70">
<header class="flex items-center gap-2.5 border-b border-tdl13-edge px-4 py-3.5"><span aria-hidden="true" class="size-8 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai3),var(--color-tdl13-ai))] shadow-[inset_0_0_0_3px_var(--color-tdl13-panel)] animate-tdl13-spin-slow"></span><div><b class="block text-sm">Copilot</b><small class="text-[11.5px] text-tdl13-mut">grounded in this board</small></div><button data-close type="button" aria-label="Close copilot" class="ml-auto grid size-9 place-items-center rounded-lg text-tdl13-mut transition-colors hover:bg-tdl13-bg hover:text-tdl13-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-2 [stroke-linecap:round]" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></header>
<div data-thread role="log" aria-live="polite" class="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto overscroll-contain p-4 text-[13px]/[1.5] [&_.q]:max-w-[85%] [&_.q]:self-end [&_.q]:rounded-[13px_13px_4px_13px] [&_.q]:bg-tdl13-q [&_.q]:px-3.25 [&_.q]:py-2.25 [&_.q]:starting:translate-y-1 [&_.q]:starting:opacity-0 [&_.q]:transition [&_.a]:max-w-[92%] [&_.a]:rounded-[4px_13px_13px_13px] [&_.a]:border-[1.5px] [&_.a]:border-transparent [&_.a]:bg-[linear-gradient(var(--color-tdl13-panel),var(--color-tdl13-panel))_padding-box,linear-gradient(130deg,var(--color-tdl13-ai),var(--color-tdl13-ai2))_border-box] [&_.a]:px-3.5 [&_.a]:py-2.75 [&_.a]:starting:translate-y-1 [&_.a]:starting:opacity-0 [&_.a]:transition [&_.a_b]:text-tdl13-aiink">
<p class="q">Why did gross margin jump this quarter?</p>
<div class="a"><p>Two drivers: hosting costs fell <b>9%</b> after the CDN renegotiation, and the Pro tier (72% margin) grew to <b>44%</b> of revenue mix — worth ~1.6 of the 2.1 point gain.</p></div>
<p class="q">Draft a one-line summary for the board deck.</p>
<div class="a" data-pending="Q3 gross margin rose 2.1 pts to 61.4%, driven by lower hosting costs and a richer Pro-tier mix."><span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span><span class="sr-only">Copilot is typing</span></div>
</div>
<div class="flex flex-wrap gap-2 px-4 pb-3 [&>button]:h-8.5 [&>button]:cursor-pointer [&>button]:rounded-full [&>button]:border [&>button]:border-tdl13-edge [&>button]:bg-tdl13-bg [&>button]:px-3 [&>button]:text-xs [&>button]:font-semibold [&>button]:text-tdl13-mut [&>button]:transition [&>button:hover]:border-tdl13-ai/50 [&>button:hover]:text-tdl13-aiink [&>button:focus-visible]:outline-2 [&>button:focus-visible]:outline-offset-2 [&>button:focus-visible]:outline-tdl13-ai">
<button type="button" data-chip>Explain NRR risk</button><button type="button" data-chip>Forecast Q4 margin</button>
</div>
<form data-form class="mx-4 mb-4 flex items-end gap-2 rounded-xl border border-tdl13-edge bg-tdl13-bg p-1.75 pl-3.5 transition focus-within:border-tdl13-ai focus-within:ring-3 focus-within:ring-tdl13-ai/15"><label for="tdl13-in" class="sr-only">Ask about this board</label><textarea id="tdl13-in" rows="1" placeholder="Ask about this board…" class="field-sizing-content max-h-28 min-h-9 flex-1 resize-none bg-transparent py-2 text-[13px] outline-none placeholder:text-tdl13-mut"></textarea><button type="submit" aria-label="Send" class="grid size-9 flex-none place-items-center rounded-lg bg-linear-130 from-tdl13-ai to-tdl13-ai2 text-white transition hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-[2.4] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></form>
</div>
</aside>
</div>
</section><section class="tdl-13 @container/s w-full min-h-svh bg-tdl13-bg font-tdl13-sans text-tdl13-ink antialiased" aria-label="AI copilot split dashboard demo">
<div data-split class="group/split grid h-svh min-h-[34rem] grid-cols-[minmax(0,1fr)_21.25rem] overflow-hidden transition-[grid-template-columns] duration-350 ease-[cubic-bezier(.2,.7,.2,1)] group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem] @max-[44rem]/s:grid-cols-[minmax(0,1fr)_17.5rem]">
<div class="flex min-w-0 flex-col">
<header class="flex h-15 flex-none items-center justify-between gap-3 border-b border-tdl13-edge bg-tdl13-panel px-4">
<strong class="flex min-w-0 items-center gap-2.5 truncate text-[15px] font-semibold"><span aria-hidden="true" class="size-5 flex-none rounded-md bg-[conic-gradient(from_210deg,var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))]"></span>Margin — Q3 board</strong>
<button data-ask type="button" aria-expanded="true" aria-controls="tdl13-panel" class="flex h-10 flex-none items-center gap-2 rounded-full border border-tdl13-ai/40 bg-linear-120 from-tdl13-ai/10 to-tdl13-ai2/10 px-4 text-[13px] font-semibold text-tdl13-aiink transition hover:-translate-y-px hover:shadow-[0_6px_18px_-8px_var(--color-tdl13-ai)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><i aria-hidden="true" class="size-3.5 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))] animate-tdl13-spin"></i>Copilot<kbd class="rounded border border-tdl13-ai/30 px-1 font-sans text-[10px] text-tdl13-aiink/80 @max-[44rem]/s:hidden">⌘J</kbd></button>
</header>
<div class="grid min-h-0 flex-1 auto-rows-min grid-cols-[repeat(auto-fit,minmax(min(10.5rem,100%),1fr))] gap-3 overflow-y-auto overscroll-contain p-4 [&>article]:rounded-2xl [&>article]:border [&>article]:border-tdl13-edge [&>article]:bg-tdl13-panel [&>article]:px-4.5 [&>article]:py-4 [&_h3]:text-[10.5px] [&_h3]:font-bold [&_h3]:tracking-[0.1em] [&_h3]:text-tdl13-mut [&_h3]:uppercase">
<article><h3>Gross margin</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">61.4%</p><small class="text-[11.5px] font-semibold text-tdl13-good">▲ 2.1 pts QoQ</small></article>
<article><h3>Burn multiple</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">1.3×</p><small class="text-[11.5px] font-semibold text-tdl13-good">improving</small></article>
<article><h3>Net revenue retention</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">117%</p><small class="text-[11.5px] font-semibold text-tdl13-mut">flat QoQ</small></article>
<article class="col-span-full"><h3 class="flex flex-wrap items-center justify-between gap-2">Revenue vs. cost<span class="flex gap-3 tracking-normal normal-case"><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai"></i>Revenue</span><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai2/55"></i>Cost</span></span></h3>
<div aria-hidden="true" class="mt-3 flex h-28 items-end gap-2.25 *:relative *:h-full *:flex-1 *:before:absolute *:before:bottom-0 *:before:left-0 *:before:h-(--a) *:before:w-[52%] *:before:rounded-t-[3px] *:before:bg-tdl13-ai *:before:content-[''] *:after:absolute *:after:right-0 *:after:bottom-0 *:after:h-(--b) *:after:w-[34%] *:after:rounded-t-[3px] *:after:bg-tdl13-ai2/55 *:after:content-['']"><i style="--a:46%;--b:30%"></i><i style="--a:52%;--b:33%"></i><i style="--a:49%;--b:35%"></i><i style="--a:61%;--b:36%"></i><i style="--a:66%;--b:38%"></i><i style="--a:74%;--b:39%"></i><i style="--a:82%;--b:41%"></i><i style="--a:90%;--b:42%"></i></div>
</article>
</div>
</div>
<aside id="tdl13-panel" aria-label="AI copilot" class="min-w-0 overflow-hidden border-l border-tdl13-edge bg-tdl13-panel transition-[visibility] group-data-closed/split:invisible group-data-closed/split:delay-350">
<div class="flex h-full w-85 flex-col @max-[44rem]/s:w-70">
<header class="flex items-center gap-2.5 border-b border-tdl13-edge px-4 py-3.5"><span aria-hidden="true" class="size-8 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai3),var(--color-tdl13-ai))] shadow-[inset_0_0_0_3px_var(--color-tdl13-panel)] animate-tdl13-spin-slow"></span><div><b class="block text-sm">Copilot</b><small class="text-[11.5px] text-tdl13-mut">grounded in this board</small></div><button data-close type="button" aria-label="Close copilot" class="ml-auto grid size-9 place-items-center rounded-lg text-tdl13-mut transition-colors hover:bg-tdl13-bg hover:text-tdl13-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-2 [stroke-linecap:round]" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></header>
<div data-thread role="log" aria-live="polite" class="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto overscroll-contain p-4 text-[13px]/[1.5] [&_.q]:max-w-[85%] [&_.q]:self-end [&_.q]:rounded-[13px_13px_4px_13px] [&_.q]:bg-tdl13-q [&_.q]:px-3.25 [&_.q]:py-2.25 [&_.q]:starting:translate-y-1 [&_.q]:starting:opacity-0 [&_.q]:transition [&_.a]:max-w-[92%] [&_.a]:rounded-[4px_13px_13px_13px] [&_.a]:border-[1.5px] [&_.a]:border-transparent [&_.a]:bg-[linear-gradient(var(--color-tdl13-panel),var(--color-tdl13-panel))_padding-box,linear-gradient(130deg,var(--color-tdl13-ai),var(--color-tdl13-ai2))_border-box] [&_.a]:px-3.5 [&_.a]:py-2.75 [&_.a]:starting:translate-y-1 [&_.a]:starting:opacity-0 [&_.a]:transition [&_.a_b]:text-tdl13-aiink">
<p class="q">Why did gross margin jump this quarter?</p>
<div class="a"><p>Two drivers: hosting costs fell <b>9%</b> after the CDN renegotiation, and the Pro tier (72% margin) grew to <b>44%</b> of revenue mix — worth ~1.6 of the 2.1 point gain.</p></div>
<p class="q">Draft a one-line summary for the board deck.</p>
<div class="a" data-pending="Q3 gross margin rose 2.1 pts to 61.4%, driven by lower hosting costs and a richer Pro-tier mix."><span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span><span class="sr-only">Copilot is typing</span></div>
</div>
<div class="flex flex-wrap gap-2 px-4 pb-3 [&>button]:h-8.5 [&>button]:cursor-pointer [&>button]:rounded-full [&>button]:border [&>button]:border-tdl13-edge [&>button]:bg-tdl13-bg [&>button]:px-3 [&>button]:text-xs [&>button]:font-semibold [&>button]:text-tdl13-mut [&>button]:transition [&>button:hover]:border-tdl13-ai/50 [&>button:hover]:text-tdl13-aiink [&>button:focus-visible]:outline-2 [&>button:focus-visible]:outline-offset-2 [&>button:focus-visible]:outline-tdl13-ai">
<button type="button" data-chip>Explain NRR risk</button><button type="button" data-chip>Forecast Q4 margin</button>
</div>
<form data-form class="mx-4 mb-4 flex items-end gap-2 rounded-xl border border-tdl13-edge bg-tdl13-bg p-1.75 pl-3.5 transition focus-within:border-tdl13-ai focus-within:ring-3 focus-within:ring-tdl13-ai/15"><label for="tdl13-in" class="sr-only">Ask about this board</label><textarea id="tdl13-in" rows="1" placeholder="Ask about this board…" class="field-sizing-content max-h-28 min-h-9 flex-1 resize-none bg-transparent py-2 text-[13px] outline-none placeholder:text-tdl13-mut"></textarea><button type="submit" aria-label="Send" class="grid size-9 flex-none place-items-center rounded-lg bg-linear-130 from-tdl13-ai to-tdl13-ai2 text-white transition hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-[2.4] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></form>
</div>
</aside>
</div>
</section>@import "tailwindcss";
@theme {
--color-tdl13-bg: #f8f7fb;
--color-tdl13-bg: oklch(0.975 0.005 300);
--color-tdl13-panel: #ffffff;
--color-tdl13-panel: oklch(1 0 0);
--color-tdl13-q: #efedf4;
--color-tdl13-q: oklch(0.945 0.01 300);
--color-tdl13-edge: #e3e0ea;
--color-tdl13-edge: oklch(0.905 0.012 300);
--color-tdl13-ink: #29243a;
--color-tdl13-ink: oklch(0.27 0.025 300);
--color-tdl13-mut: #5f5a6d;
--color-tdl13-mut: oklch(0.49 0.025 300);
--color-tdl13-ai: #9a48e0;
--color-tdl13-ai: oklch(0.58 0.2 305);
--color-tdl13-aiink: #6a24a8;
--color-tdl13-aiink: oklch(0.45 0.18 305);
--color-tdl13-ai2: #3ea6dc;
--color-tdl13-ai2: oklch(0.69 0.13 230);
--color-tdl13-ai3: #ee8fb4;
--color-tdl13-ai3: oklch(0.76 0.12 355);
--color-tdl13-good: #17804a;
--color-tdl13-good: oklch(0.52 0.13 155);
--font-tdl13-sans: 'Bricolage Grotesque', system-ui, sans-serif;
--animate-tdl13-spin: tdl13-spin 6s linear infinite;
--animate-tdl13-spin-slow: tdl13-spin 9s linear infinite;
--animate-tdl13-think: tdl13-think 1.2s infinite;
@keyframes tdl13-spin {
to {
--tdl13-spin: 570deg;
}
}
@keyframes tdl13-think {
0%,
60%,
100% {
translate: 0 0;
opacity: .4;
}
30% {
translate: 0 -4px;
opacity: 1;
}
}
}
/* At-rule: an angle only animates once it is registered. */
@property --tdl13-spin {
syntax: "<angle>";
inherits: false;
initial-value: 210deg;
}
.tdl-13 {
width: 100%;
min-height: 100svh;
}
@media (prefers-reduced-motion: reduce) {
.tdl-13 * {
animation: none !important;
transition-duration: 0.01s !important;
}
}@import "tailwindcss";
@theme {
--color-tdl13-bg: #f8f7fb;
--color-tdl13-bg: oklch(0.975 0.005 300);
--color-tdl13-panel: #ffffff;
--color-tdl13-panel: oklch(1 0 0);
--color-tdl13-q: #efedf4;
--color-tdl13-q: oklch(0.945 0.01 300);
--color-tdl13-edge: #e3e0ea;
--color-tdl13-edge: oklch(0.905 0.012 300);
--color-tdl13-ink: #29243a;
--color-tdl13-ink: oklch(0.27 0.025 300);
--color-tdl13-mut: #5f5a6d;
--color-tdl13-mut: oklch(0.49 0.025 300);
--color-tdl13-ai: #9a48e0;
--color-tdl13-ai: oklch(0.58 0.2 305);
--color-tdl13-aiink: #6a24a8;
--color-tdl13-aiink: oklch(0.45 0.18 305);
--color-tdl13-ai2: #3ea6dc;
--color-tdl13-ai2: oklch(0.69 0.13 230);
--color-tdl13-ai3: #ee8fb4;
--color-tdl13-ai3: oklch(0.76 0.12 355);
--color-tdl13-good: #17804a;
--color-tdl13-good: oklch(0.52 0.13 155);
--font-tdl13-sans: 'Bricolage Grotesque', system-ui, sans-serif;
--animate-tdl13-spin: tdl13-spin 6s linear infinite;
--animate-tdl13-spin-slow: tdl13-spin 9s linear infinite;
--animate-tdl13-think: tdl13-think 1.2s infinite;
@keyframes tdl13-spin {
to {
--tdl13-spin: 570deg;
}
}
@keyframes tdl13-think {
0%,
60%,
100% {
translate: 0 0;
opacity: .4;
}
30% {
translate: 0 -4px;
opacity: 1;
}
}
}
/* At-rule: an angle only animates once it is registered. */
@property --tdl13-spin {
syntax: "<angle>";
inherits: false;
initial-value: 210deg;
}
.tdl-13 {
width: 100%;
min-height: 100svh;
}
@media (prefers-reduced-motion: reduce) {
.tdl-13 * {
animation: none !important;
transition-duration: 0.01s !important;
}
}(() => {
document.querySelectorAll('.tdl-13 [data-split]').forEach((split) => {
if (split.dataset.bound) return; split.dataset.bound = '1';
const ask = split.querySelector('[data-ask]');
const thread = split.querySelector('[data-thread]');
const input = split.querySelector('textarea');
const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const answers = {
'explain nrr risk': 'NRR is flat at 117%, but two enterprise accounts (11% of ARR) renew in Q4 without expansion plans. Losing one would pull NRR to ~111%.',
'forecast q4 margin': 'If the Pro mix holds at 44% and hosting stays flat, Q4 gross margin lands at <b>62.0–62.6%</b>.'
};
const set = (open) => { split.toggleAttribute('data-closed', !open); ask.setAttribute('aria-expanded', String(open)); if (open) setTimeout(() => input.focus(), 360); else ask.focus(); };
const bottom = () => { thread.scrollTop = thread.scrollHeight; };
const stream = (bubble, html) => {
if (calm) { bubble.innerHTML = '<p>' + html + '</p>'; bottom(); return; }
const words = html.split(' '); let i = 0; bubble.innerHTML = '<p></p>'; const p = bubble.firstChild;
const t = setInterval(() => { p.innerHTML = words.slice(0, ++i).join(' '); bottom(); if (i >= words.length) clearInterval(t); }, 45);
};
const reply = (text) => {
const a = document.createElement('div'); a.className = 'a';
a.innerHTML = '<span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span>';
thread.appendChild(a); bottom();
setTimeout(() => stream(a, answers[text.toLowerCase()] || 'Good question — based on this board, I would start with the margin bridge: hosting savings and tier mix explain most of the movement.'), 900);
};
const send = (text) => {
text = text.trim(); if (!text) return;
const q = document.createElement('p'); q.className = 'q'; q.textContent = text; thread.appendChild(q);
input.value = ''; bottom(); reply(text);
};
const pending = thread.querySelector('[data-pending]');
if (pending) setTimeout(() => stream(pending, pending.dataset.pending), 1400);
ask.addEventListener('click', () => set(split.hasAttribute('data-closed')));
split.querySelector('[data-close]').addEventListener('click', () => set(false));
split.querySelector('[data-form]').addEventListener('submit', (e) => { e.preventDefault(); send(input.value); });
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(input.value); } });
split.querySelectorAll('[data-chip]').forEach((c) => c.addEventListener('click', () => send(c.textContent)));
split.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !split.hasAttribute('data-closed')) set(false); });
document.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'j') { e.preventDefault(); set(split.hasAttribute('data-closed')); } });
});
})();(() => {
document.querySelectorAll('.tdl-13 [data-split]').forEach((split) => {
if (split.dataset.bound) return; split.dataset.bound = '1';
const ask = split.querySelector('[data-ask]');
const thread = split.querySelector('[data-thread]');
const input = split.querySelector('textarea');
const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const answers = {
'explain nrr risk': 'NRR is flat at 117%, but two enterprise accounts (11% of ARR) renew in Q4 without expansion plans. Losing one would pull NRR to ~111%.',
'forecast q4 margin': 'If the Pro mix holds at 44% and hosting stays flat, Q4 gross margin lands at <b>62.0–62.6%</b>.'
};
const set = (open) => { split.toggleAttribute('data-closed', !open); ask.setAttribute('aria-expanded', String(open)); if (open) setTimeout(() => input.focus(), 360); else ask.focus(); };
const bottom = () => { thread.scrollTop = thread.scrollHeight; };
const stream = (bubble, html) => {
if (calm) { bubble.innerHTML = '<p>' + html + '</p>'; bottom(); return; }
const words = html.split(' '); let i = 0; bubble.innerHTML = '<p></p>'; const p = bubble.firstChild;
const t = setInterval(() => { p.innerHTML = words.slice(0, ++i).join(' '); bottom(); if (i >= words.length) clearInterval(t); }, 45);
};
const reply = (text) => {
const a = document.createElement('div'); a.className = 'a';
a.innerHTML = '<span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span>';
thread.appendChild(a); bottom();
setTimeout(() => stream(a, answers[text.toLowerCase()] || 'Good question — based on this board, I would start with the margin bridge: hosting savings and tier mix explain most of the movement.'), 900);
};
const send = (text) => {
text = text.trim(); if (!text) return;
const q = document.createElement('p'); q.className = 'q'; q.textContent = text; thread.appendChild(q);
input.value = ''; bottom(); reply(text);
};
const pending = thread.querySelector('[data-pending]');
if (pending) setTimeout(() => stream(pending, pending.dataset.pending), 1400);
ask.addEventListener('click', () => set(split.hasAttribute('data-closed')));
split.querySelector('[data-close]').addEventListener('click', () => set(false));
split.querySelector('[data-form]').addEventListener('submit', (e) => { e.preventDefault(); send(input.value); });
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(input.value); } });
split.querySelectorAll('[data-chip]').forEach((c) => c.addEventListener('click', () => send(c.textContent)));
split.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !split.hasAttribute('data-closed')) set(false); });
document.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'j') { e.preventDefault(); set(split.hasAttribute('data-closed')); } });
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tdl13-bg: #f8f7fb;
--color-tdl13-bg: oklch(0.975 0.005 300);
--color-tdl13-panel: #ffffff;
--color-tdl13-panel: oklch(1 0 0);
--color-tdl13-q: #efedf4;
--color-tdl13-q: oklch(0.945 0.01 300);
--color-tdl13-edge: #e3e0ea;
--color-tdl13-edge: oklch(0.905 0.012 300);
--color-tdl13-ink: #29243a;
--color-tdl13-ink: oklch(0.27 0.025 300);
--color-tdl13-mut: #5f5a6d;
--color-tdl13-mut: oklch(0.49 0.025 300);
--color-tdl13-ai: #9a48e0;
--color-tdl13-ai: oklch(0.58 0.2 305);
--color-tdl13-aiink: #6a24a8;
--color-tdl13-aiink: oklch(0.45 0.18 305);
--color-tdl13-ai2: #3ea6dc;
--color-tdl13-ai2: oklch(0.69 0.13 230);
--color-tdl13-ai3: #ee8fb4;
--color-tdl13-ai3: oklch(0.76 0.12 355);
--color-tdl13-good: #17804a;
--color-tdl13-good: oklch(0.52 0.13 155);
--font-tdl13-sans: 'Bricolage Grotesque', system-ui, sans-serif;
--animate-tdl13-spin: tdl13-spin 6s linear infinite;
--animate-tdl13-spin-slow: tdl13-spin 9s linear infinite;
--animate-tdl13-think: tdl13-think 1.2s infinite;
@keyframes tdl13-spin {
to {
--tdl13-spin: 570deg;
}
}
@keyframes tdl13-think {
0%,
60%,
100% {
translate: 0 0;
opacity: .4;
}
30% {
translate: 0 -4px;
opacity: 1;
}
}
}
/* At-rule: an angle only animates once it is registered. */
@property --tdl13-spin {
syntax: "<angle>";
inherits: false;
initial-value: 210deg;
}
.tdl-13 {
width: 100%;
min-height: 100svh;
}
@media (prefers-reduced-motion: reduce) {
.tdl-13 * {
animation: none !important;
transition-duration: 0.01s !important;
}
}@import "tailwindcss";
@theme {
--color-tdl13-bg: #f8f7fb;
--color-tdl13-bg: oklch(0.975 0.005 300);
--color-tdl13-panel: #ffffff;
--color-tdl13-panel: oklch(1 0 0);
--color-tdl13-q: #efedf4;
--color-tdl13-q: oklch(0.945 0.01 300);
--color-tdl13-edge: #e3e0ea;
--color-tdl13-edge: oklch(0.905 0.012 300);
--color-tdl13-ink: #29243a;
--color-tdl13-ink: oklch(0.27 0.025 300);
--color-tdl13-mut: #5f5a6d;
--color-tdl13-mut: oklch(0.49 0.025 300);
--color-tdl13-ai: #9a48e0;
--color-tdl13-ai: oklch(0.58 0.2 305);
--color-tdl13-aiink: #6a24a8;
--color-tdl13-aiink: oklch(0.45 0.18 305);
--color-tdl13-ai2: #3ea6dc;
--color-tdl13-ai2: oklch(0.69 0.13 230);
--color-tdl13-ai3: #ee8fb4;
--color-tdl13-ai3: oklch(0.76 0.12 355);
--color-tdl13-good: #17804a;
--color-tdl13-good: oklch(0.52 0.13 155);
--font-tdl13-sans: 'Bricolage Grotesque', system-ui, sans-serif;
--animate-tdl13-spin: tdl13-spin 6s linear infinite;
--animate-tdl13-spin-slow: tdl13-spin 9s linear infinite;
--animate-tdl13-think: tdl13-think 1.2s infinite;
@keyframes tdl13-spin {
to {
--tdl13-spin: 570deg;
}
}
@keyframes tdl13-think {
0%,
60%,
100% {
translate: 0 0;
opacity: .4;
}
30% {
translate: 0 -4px;
opacity: 1;
}
}
}
/* At-rule: an angle only animates once it is registered. */
@property --tdl13-spin {
syntax: "<angle>";
inherits: false;
initial-value: 210deg;
}
.tdl-13 {
width: 100%;
min-height: 100svh;
}
@media (prefers-reduced-motion: reduce) {
.tdl-13 * {
animation: none !important;
transition-duration: 0.01s !important;
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400..700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400..700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.cdl-13__split[data-closed]rules becamegroup-data-closed/split:variants; the two-bar chart's::before/::afterbars are*:before:/*:after:child variants reading inline--a/--b.- Only
@property --tdl13-spinstays CSS — an at-rule has no utility form. Its keyframes live inside@themenext to--animate-tdl13-spin. - UI upgrade from the CSS original: the copilot is interactive — the pending answer streams in, chips and the composer post real messages with a typing indicator and canned replies, Enter sends, ⌘/Ctrl J toggles and Esc closes. The thread is an
aria-livelog. - UI upgrade: the fake input span is a labelled auto-growing textarea; the chart gained a legend; the close button is a crisp SVG at 36px with focus return to the Copilot button; AI ink darkened for 7:1 contrast.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS AI Copilot Chat Sidebar Dashboard Layout
Source: https://codefronts.com/layouts/tailwind-css-dashboard-layouts/tailwind-css-ai-copilot-chat-sidebar-dashboard-layout/
Your data on the left, a resident AI copilot docked on the right. The panel folds by animating grid-cols-[minmax(0,1fr)_21.25rem] to group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem], so the dashboard reflows in the same motion. New in the port: the copilot actually talks — the pending answer streams in word by word, suggestion chips and the composer send real messages, and Esc closes the panel.
## HTML
```html
<section class="tdl-13 @container/s w-full min-h-svh bg-tdl13-bg font-tdl13-sans text-tdl13-ink antialiased" aria-label="AI copilot split dashboard demo">
<div data-split class="group/split grid h-svh min-h-[34rem] grid-cols-[minmax(0,1fr)_21.25rem] overflow-hidden transition-[grid-template-columns] duration-350 ease-[cubic-bezier(.2,.7,.2,1)] group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem] @max-[44rem]/s:grid-cols-[minmax(0,1fr)_17.5rem]">
<div class="flex min-w-0 flex-col">
<header class="flex h-15 flex-none items-center justify-between gap-3 border-b border-tdl13-edge bg-tdl13-panel px-4">
<strong class="flex min-w-0 items-center gap-2.5 truncate text-[15px] font-semibold"><span aria-hidden="true" class="size-5 flex-none rounded-md bg-[conic-gradient(from_210deg,var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))]"></span>Margin — Q3 board</strong>
<button data-ask type="button" aria-expanded="true" aria-controls="tdl13-panel" class="flex h-10 flex-none items-center gap-2 rounded-full border border-tdl13-ai/40 bg-linear-120 from-tdl13-ai/10 to-tdl13-ai2/10 px-4 text-[13px] font-semibold text-tdl13-aiink transition hover:-translate-y-px hover:shadow-[0_6px_18px_-8px_var(--color-tdl13-ai)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><i aria-hidden="true" class="size-3.5 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))] animate-tdl13-spin"></i>Copilot<kbd class="rounded border border-tdl13-ai/30 px-1 font-sans text-[10px] text-tdl13-aiink/80 @max-[44rem]/s:hidden">⌘J</kbd></button>
</header>
<div class="grid min-h-0 flex-1 auto-rows-min grid-cols-[repeat(auto-fit,minmax(min(10.5rem,100%),1fr))] gap-3 overflow-y-auto overscroll-contain p-4 [&>article]:rounded-2xl [&>article]:border [&>article]:border-tdl13-edge [&>article]:bg-tdl13-panel [&>article]:px-4.5 [&>article]:py-4 [&_h3]:text-[10.5px] [&_h3]:font-bold [&_h3]:tracking-[0.1em] [&_h3]:text-tdl13-mut [&_h3]:uppercase">
<article><h3>Gross margin</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">61.4%</p><small class="text-[11.5px] font-semibold text-tdl13-good">▲ 2.1 pts QoQ</small></article>
<article><h3>Burn multiple</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">1.3×</p><small class="text-[11.5px] font-semibold text-tdl13-good">improving</small></article>
<article><h3>Net revenue retention</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">117%</p><small class="text-[11.5px] font-semibold text-tdl13-mut">flat QoQ</small></article>
<article class="col-span-full"><h3 class="flex flex-wrap items-center justify-between gap-2">Revenue vs. cost<span class="flex gap-3 tracking-normal normal-case"><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai"></i>Revenue</span><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai2/55"></i>Cost</span></span></h3>
<div aria-hidden="true" class="mt-3 flex h-28 items-end gap-2.25 *:relative *:h-full *:flex-1 *:before:absolute *:before:bottom-0 *:before:left-0 *:before:h-(--a) *:before:w-[52%] *:before:rounded-t-[3px] *:before:bg-tdl13-ai *:before:content-[''] *:after:absolute *:after:right-0 *:after:bottom-0 *:after:h-(--b) *:after:w-[34%] *:after:rounded-t-[3px] *:after:bg-tdl13-ai2/55 *:after:content-['']"><i style="--a:46%;--b:30%"></i><i style="--a:52%;--b:33%"></i><i style="--a:49%;--b:35%"></i><i style="--a:61%;--b:36%"></i><i style="--a:66%;--b:38%"></i><i style="--a:74%;--b:39%"></i><i style="--a:82%;--b:41%"></i><i style="--a:90%;--b:42%"></i></div>
</article>
</div>
</div>
<aside id="tdl13-panel" aria-label="AI copilot" class="min-w-0 overflow-hidden border-l border-tdl13-edge bg-tdl13-panel transition-[visibility] group-data-closed/split:invisible group-data-closed/split:delay-350">
<div class="flex h-full w-85 flex-col @max-[44rem]/s:w-70">
<header class="flex items-center gap-2.5 border-b border-tdl13-edge px-4 py-3.5"><span aria-hidden="true" class="size-8 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai3),var(--color-tdl13-ai))] shadow-[inset_0_0_0_3px_var(--color-tdl13-panel)] animate-tdl13-spin-slow"></span><div><b class="block text-sm">Copilot</b><small class="text-[11.5px] text-tdl13-mut">grounded in this board</small></div><button data-close type="button" aria-label="Close copilot" class="ml-auto grid size-9 place-items-center rounded-lg text-tdl13-mut transition-colors hover:bg-tdl13-bg hover:text-tdl13-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-2 [stroke-linecap:round]" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></header>
<div data-thread role="log" aria-live="polite" class="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto overscroll-contain p-4 text-[13px]/[1.5] [&_.q]:max-w-[85%] [&_.q]:self-end [&_.q]:rounded-[13px_13px_4px_13px] [&_.q]:bg-tdl13-q [&_.q]:px-3.25 [&_.q]:py-2.25 [&_.q]:starting:translate-y-1 [&_.q]:starting:opacity-0 [&_.q]:transition [&_.a]:max-w-[92%] [&_.a]:rounded-[4px_13px_13px_13px] [&_.a]:border-[1.5px] [&_.a]:border-transparent [&_.a]:bg-[linear-gradient(var(--color-tdl13-panel),var(--color-tdl13-panel))_padding-box,linear-gradient(130deg,var(--color-tdl13-ai),var(--color-tdl13-ai2))_border-box] [&_.a]:px-3.5 [&_.a]:py-2.75 [&_.a]:starting:translate-y-1 [&_.a]:starting:opacity-0 [&_.a]:transition [&_.a_b]:text-tdl13-aiink">
<p class="q">Why did gross margin jump this quarter?</p>
<div class="a"><p>Two drivers: hosting costs fell <b>9%</b> after the CDN renegotiation, and the Pro tier (72% margin) grew to <b>44%</b> of revenue mix — worth ~1.6 of the 2.1 point gain.</p></div>
<p class="q">Draft a one-line summary for the board deck.</p>
<div class="a" data-pending="Q3 gross margin rose 2.1 pts to 61.4%, driven by lower hosting costs and a richer Pro-tier mix."><span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span><span class="sr-only">Copilot is typing</span></div>
</div>
<div class="flex flex-wrap gap-2 px-4 pb-3 [&>button]:h-8.5 [&>button]:cursor-pointer [&>button]:rounded-full [&>button]:border [&>button]:border-tdl13-edge [&>button]:bg-tdl13-bg [&>button]:px-3 [&>button]:text-xs [&>button]:font-semibold [&>button]:text-tdl13-mut [&>button]:transition [&>button:hover]:border-tdl13-ai/50 [&>button:hover]:text-tdl13-aiink [&>button:focus-visible]:outline-2 [&>button:focus-visible]:outline-offset-2 [&>button:focus-visible]:outline-tdl13-ai">
<button type="button" data-chip>Explain NRR risk</button><button type="button" data-chip>Forecast Q4 margin</button>
</div>
<form data-form class="mx-4 mb-4 flex items-end gap-2 rounded-xl border border-tdl13-edge bg-tdl13-bg p-1.75 pl-3.5 transition focus-within:border-tdl13-ai focus-within:ring-3 focus-within:ring-tdl13-ai/15"><label for="tdl13-in" class="sr-only">Ask about this board</label><textarea id="tdl13-in" rows="1" placeholder="Ask about this board…" class="field-sizing-content max-h-28 min-h-9 flex-1 resize-none bg-transparent py-2 text-[13px] outline-none placeholder:text-tdl13-mut"></textarea><button type="submit" aria-label="Send" class="grid size-9 flex-none place-items-center rounded-lg bg-linear-130 from-tdl13-ai to-tdl13-ai2 text-white transition hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-[2.4] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></form>
</div>
</aside>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tdl13-bg: #f8f7fb;
--color-tdl13-bg: oklch(0.975 0.005 300);
--color-tdl13-panel: #ffffff;
--color-tdl13-panel: oklch(1 0 0);
--color-tdl13-q: #efedf4;
--color-tdl13-q: oklch(0.945 0.01 300);
--color-tdl13-edge: #e3e0ea;
--color-tdl13-edge: oklch(0.905 0.012 300);
--color-tdl13-ink: #29243a;
--color-tdl13-ink: oklch(0.27 0.025 300);
--color-tdl13-mut: #5f5a6d;
--color-tdl13-mut: oklch(0.49 0.025 300);
--color-tdl13-ai: #9a48e0;
--color-tdl13-ai: oklch(0.58 0.2 305);
--color-tdl13-aiink: #6a24a8;
--color-tdl13-aiink: oklch(0.45 0.18 305);
--color-tdl13-ai2: #3ea6dc;
--color-tdl13-ai2: oklch(0.69 0.13 230);
--color-tdl13-ai3: #ee8fb4;
--color-tdl13-ai3: oklch(0.76 0.12 355);
--color-tdl13-good: #17804a;
--color-tdl13-good: oklch(0.52 0.13 155);
--font-tdl13-sans: 'Bricolage Grotesque', system-ui, sans-serif;
--animate-tdl13-spin: tdl13-spin 6s linear infinite;
--animate-tdl13-spin-slow: tdl13-spin 9s linear infinite;
--animate-tdl13-think: tdl13-think 1.2s infinite;
@keyframes tdl13-spin {
to {
--tdl13-spin: 570deg;
}
}
@keyframes tdl13-think {
0%,
60%,
100% {
translate: 0 0;
opacity: .4;
}
30% {
translate: 0 -4px;
opacity: 1;
}
}
}
/* At-rule: an angle only animates once it is registered. */
@property --tdl13-spin {
syntax: "<angle>";
inherits: false;
initial-value: 210deg;
}
.tdl-13 {
width: 100%;
min-height: 100svh;
}
@media (prefers-reduced-motion: reduce) {
.tdl-13 * {
animation: none !important;
transition-duration: 0.01s !important;
}
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('.tdl-13 [data-split]').forEach((split) => {
if (split.dataset.bound) return; split.dataset.bound = '1';
const ask = split.querySelector('[data-ask]');
const thread = split.querySelector('[data-thread]');
const input = split.querySelector('textarea');
const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const answers = {
'explain nrr risk': 'NRR is flat at 117%, but two enterprise accounts (11% of ARR) renew in Q4 without expansion plans. Losing one would pull NRR to ~111%.',
'forecast q4 margin': 'If the Pro mix holds at 44% and hosting stays flat, Q4 gross margin lands at <b>62.0–62.6%</b>.'
};
const set = (open) => { split.toggleAttribute('data-closed', !open); ask.setAttribute('aria-expanded', String(open)); if (open) setTimeout(() => input.focus(), 360); else ask.focus(); };
const bottom = () => { thread.scrollTop = thread.scrollHeight; };
const stream = (bubble, html) => {
if (calm) { bubble.innerHTML = '<p>' + html + '</p>'; bottom(); return; }
const words = html.split(' '); let i = 0; bubble.innerHTML = '<p></p>'; const p = bubble.firstChild;
const t = setInterval(() => { p.innerHTML = words.slice(0, ++i).join(' '); bottom(); if (i >= words.length) clearInterval(t); }, 45);
};
const reply = (text) => {
const a = document.createElement('div'); a.className = 'a';
a.innerHTML = '<span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span>';
thread.appendChild(a); bottom();
setTimeout(() => stream(a, answers[text.toLowerCase()] || 'Good question — based on this board, I would start with the margin bridge: hosting savings and tier mix explain most of the movement.'), 900);
};
const send = (text) => {
text = text.trim(); if (!text) return;
const q = document.createElement('p'); q.className = 'q'; q.textContent = text; thread.appendChild(q);
input.value = ''; bottom(); reply(text);
};
const pending = thread.querySelector('[data-pending]');
if (pending) setTimeout(() => stream(pending, pending.dataset.pending), 1400);
ask.addEventListener('click', () => set(split.hasAttribute('data-closed')));
split.querySelector('[data-close]').addEventListener('click', () => set(false));
split.querySelector('[data-form]').addEventListener('submit', (e) => { e.preventDefault(); send(input.value); });
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(input.value); } });
split.querySelectorAll('[data-chip]').forEach((c) => c.addEventListener('click', () => send(c.textContent)));
split.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !split.hasAttribute('data-closed')) set(false); });
document.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'j') { e.preventDefault(); set(split.hasAttribute('data-closed')); } });
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind CSS AI Copilot Chat Sidebar Dashboard Layout
Source: https://codefronts.com/layouts/tailwind-css-dashboard-layouts/tailwind-css-ai-copilot-chat-sidebar-dashboard-layout/
Your data on the left, a resident AI copilot docked on the right. The panel folds by animating grid-cols-[minmax(0,1fr)_21.25rem] to group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem], so the dashboard reflows in the same motion. New in the port: the copilot actually talks — the pending answer streams in word by word, suggestion chips and the composer send real messages, and Esc closes the panel.
## HTML
```html
<section class="tdl-13 @container/s w-full min-h-svh bg-tdl13-bg font-tdl13-sans text-tdl13-ink antialiased" aria-label="AI copilot split dashboard demo">
<div data-split class="group/split grid h-svh min-h-[34rem] grid-cols-[minmax(0,1fr)_21.25rem] overflow-hidden transition-[grid-template-columns] duration-350 ease-[cubic-bezier(.2,.7,.2,1)] group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem] @max-[44rem]/s:grid-cols-[minmax(0,1fr)_17.5rem]">
<div class="flex min-w-0 flex-col">
<header class="flex h-15 flex-none items-center justify-between gap-3 border-b border-tdl13-edge bg-tdl13-panel px-4">
<strong class="flex min-w-0 items-center gap-2.5 truncate text-[15px] font-semibold"><span aria-hidden="true" class="size-5 flex-none rounded-md bg-[conic-gradient(from_210deg,var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))]"></span>Margin — Q3 board</strong>
<button data-ask type="button" aria-expanded="true" aria-controls="tdl13-panel" class="flex h-10 flex-none items-center gap-2 rounded-full border border-tdl13-ai/40 bg-linear-120 from-tdl13-ai/10 to-tdl13-ai2/10 px-4 text-[13px] font-semibold text-tdl13-aiink transition hover:-translate-y-px hover:shadow-[0_6px_18px_-8px_var(--color-tdl13-ai)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><i aria-hidden="true" class="size-3.5 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai))] animate-tdl13-spin"></i>Copilot<kbd class="rounded border border-tdl13-ai/30 px-1 font-sans text-[10px] text-tdl13-aiink/80 @max-[44rem]/s:hidden">⌘J</kbd></button>
</header>
<div class="grid min-h-0 flex-1 auto-rows-min grid-cols-[repeat(auto-fit,minmax(min(10.5rem,100%),1fr))] gap-3 overflow-y-auto overscroll-contain p-4 [&>article]:rounded-2xl [&>article]:border [&>article]:border-tdl13-edge [&>article]:bg-tdl13-panel [&>article]:px-4.5 [&>article]:py-4 [&_h3]:text-[10.5px] [&_h3]:font-bold [&_h3]:tracking-[0.1em] [&_h3]:text-tdl13-mut [&_h3]:uppercase">
<article><h3>Gross margin</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">61.4%</p><small class="text-[11.5px] font-semibold text-tdl13-good">▲ 2.1 pts QoQ</small></article>
<article><h3>Burn multiple</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">1.3×</p><small class="text-[11.5px] font-semibold text-tdl13-good">improving</small></article>
<article><h3>Net revenue retention</h3><p class="mt-1.5 mb-0.5 text-[24px] font-bold tabular-nums">117%</p><small class="text-[11.5px] font-semibold text-tdl13-mut">flat QoQ</small></article>
<article class="col-span-full"><h3 class="flex flex-wrap items-center justify-between gap-2">Revenue vs. cost<span class="flex gap-3 tracking-normal normal-case"><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai"></i>Revenue</span><span class="flex items-center gap-1.5"><i aria-hidden="true" class="size-2.5 rounded-[3px] bg-tdl13-ai2/55"></i>Cost</span></span></h3>
<div aria-hidden="true" class="mt-3 flex h-28 items-end gap-2.25 *:relative *:h-full *:flex-1 *:before:absolute *:before:bottom-0 *:before:left-0 *:before:h-(--a) *:before:w-[52%] *:before:rounded-t-[3px] *:before:bg-tdl13-ai *:before:content-[''] *:after:absolute *:after:right-0 *:after:bottom-0 *:after:h-(--b) *:after:w-[34%] *:after:rounded-t-[3px] *:after:bg-tdl13-ai2/55 *:after:content-['']"><i style="--a:46%;--b:30%"></i><i style="--a:52%;--b:33%"></i><i style="--a:49%;--b:35%"></i><i style="--a:61%;--b:36%"></i><i style="--a:66%;--b:38%"></i><i style="--a:74%;--b:39%"></i><i style="--a:82%;--b:41%"></i><i style="--a:90%;--b:42%"></i></div>
</article>
</div>
</div>
<aside id="tdl13-panel" aria-label="AI copilot" class="min-w-0 overflow-hidden border-l border-tdl13-edge bg-tdl13-panel transition-[visibility] group-data-closed/split:invisible group-data-closed/split:delay-350">
<div class="flex h-full w-85 flex-col @max-[44rem]/s:w-70">
<header class="flex items-center gap-2.5 border-b border-tdl13-edge px-4 py-3.5"><span aria-hidden="true" class="size-8 rounded-full bg-[conic-gradient(from_var(--tdl13-spin),var(--color-tdl13-ai),var(--color-tdl13-ai2),var(--color-tdl13-ai3),var(--color-tdl13-ai))] shadow-[inset_0_0_0_3px_var(--color-tdl13-panel)] animate-tdl13-spin-slow"></span><div><b class="block text-sm">Copilot</b><small class="text-[11.5px] text-tdl13-mut">grounded in this board</small></div><button data-close type="button" aria-label="Close copilot" class="ml-auto grid size-9 place-items-center rounded-lg text-tdl13-mut transition-colors hover:bg-tdl13-bg hover:text-tdl13-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-2 [stroke-linecap:round]" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></header>
<div data-thread role="log" aria-live="polite" class="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto overscroll-contain p-4 text-[13px]/[1.5] [&_.q]:max-w-[85%] [&_.q]:self-end [&_.q]:rounded-[13px_13px_4px_13px] [&_.q]:bg-tdl13-q [&_.q]:px-3.25 [&_.q]:py-2.25 [&_.q]:starting:translate-y-1 [&_.q]:starting:opacity-0 [&_.q]:transition [&_.a]:max-w-[92%] [&_.a]:rounded-[4px_13px_13px_13px] [&_.a]:border-[1.5px] [&_.a]:border-transparent [&_.a]:bg-[linear-gradient(var(--color-tdl13-panel),var(--color-tdl13-panel))_padding-box,linear-gradient(130deg,var(--color-tdl13-ai),var(--color-tdl13-ai2))_border-box] [&_.a]:px-3.5 [&_.a]:py-2.75 [&_.a]:starting:translate-y-1 [&_.a]:starting:opacity-0 [&_.a]:transition [&_.a_b]:text-tdl13-aiink">
<p class="q">Why did gross margin jump this quarter?</p>
<div class="a"><p>Two drivers: hosting costs fell <b>9%</b> after the CDN renegotiation, and the Pro tier (72% margin) grew to <b>44%</b> of revenue mix — worth ~1.6 of the 2.1 point gain.</p></div>
<p class="q">Draft a one-line summary for the board deck.</p>
<div class="a" data-pending="Q3 gross margin rose 2.1 pts to 61.4%, driven by lower hosting costs and a richer Pro-tier mix."><span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span><span class="sr-only">Copilot is typing</span></div>
</div>
<div class="flex flex-wrap gap-2 px-4 pb-3 [&>button]:h-8.5 [&>button]:cursor-pointer [&>button]:rounded-full [&>button]:border [&>button]:border-tdl13-edge [&>button]:bg-tdl13-bg [&>button]:px-3 [&>button]:text-xs [&>button]:font-semibold [&>button]:text-tdl13-mut [&>button]:transition [&>button:hover]:border-tdl13-ai/50 [&>button:hover]:text-tdl13-aiink [&>button:focus-visible]:outline-2 [&>button:focus-visible]:outline-offset-2 [&>button:focus-visible]:outline-tdl13-ai">
<button type="button" data-chip>Explain NRR risk</button><button type="button" data-chip>Forecast Q4 margin</button>
</div>
<form data-form class="mx-4 mb-4 flex items-end gap-2 rounded-xl border border-tdl13-edge bg-tdl13-bg p-1.75 pl-3.5 transition focus-within:border-tdl13-ai focus-within:ring-3 focus-within:ring-tdl13-ai/15"><label for="tdl13-in" class="sr-only">Ask about this board</label><textarea id="tdl13-in" rows="1" placeholder="Ask about this board…" class="field-sizing-content max-h-28 min-h-9 flex-1 resize-none bg-transparent py-2 text-[13px] outline-none placeholder:text-tdl13-mut"></textarea><button type="submit" aria-label="Send" class="grid size-9 flex-none place-items-center rounded-lg bg-linear-130 from-tdl13-ai to-tdl13-ai2 text-white transition hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdl13-ai"><svg viewBox="0 0 24 24" class="size-4 fill-none stroke-current stroke-[2.4] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg></button></form>
</div>
</aside>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tdl13-bg: #f8f7fb;
--color-tdl13-bg: oklch(0.975 0.005 300);
--color-tdl13-panel: #ffffff;
--color-tdl13-panel: oklch(1 0 0);
--color-tdl13-q: #efedf4;
--color-tdl13-q: oklch(0.945 0.01 300);
--color-tdl13-edge: #e3e0ea;
--color-tdl13-edge: oklch(0.905 0.012 300);
--color-tdl13-ink: #29243a;
--color-tdl13-ink: oklch(0.27 0.025 300);
--color-tdl13-mut: #5f5a6d;
--color-tdl13-mut: oklch(0.49 0.025 300);
--color-tdl13-ai: #9a48e0;
--color-tdl13-ai: oklch(0.58 0.2 305);
--color-tdl13-aiink: #6a24a8;
--color-tdl13-aiink: oklch(0.45 0.18 305);
--color-tdl13-ai2: #3ea6dc;
--color-tdl13-ai2: oklch(0.69 0.13 230);
--color-tdl13-ai3: #ee8fb4;
--color-tdl13-ai3: oklch(0.76 0.12 355);
--color-tdl13-good: #17804a;
--color-tdl13-good: oklch(0.52 0.13 155);
--font-tdl13-sans: 'Bricolage Grotesque', system-ui, sans-serif;
--animate-tdl13-spin: tdl13-spin 6s linear infinite;
--animate-tdl13-spin-slow: tdl13-spin 9s linear infinite;
--animate-tdl13-think: tdl13-think 1.2s infinite;
@keyframes tdl13-spin {
to {
--tdl13-spin: 570deg;
}
}
@keyframes tdl13-think {
0%,
60%,
100% {
translate: 0 0;
opacity: .4;
}
30% {
translate: 0 -4px;
opacity: 1;
}
}
}
/* At-rule: an angle only animates once it is registered. */
@property --tdl13-spin {
syntax: "<angle>";
inherits: false;
initial-value: 210deg;
}
.tdl-13 {
width: 100%;
min-height: 100svh;
}
@media (prefers-reduced-motion: reduce) {
.tdl-13 * {
animation: none !important;
transition-duration: 0.01s !important;
}
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('.tdl-13 [data-split]').forEach((split) => {
if (split.dataset.bound) return; split.dataset.bound = '1';
const ask = split.querySelector('[data-ask]');
const thread = split.querySelector('[data-thread]');
const input = split.querySelector('textarea');
const calm = matchMedia('(prefers-reduced-motion: reduce)').matches;
const answers = {
'explain nrr risk': 'NRR is flat at 117%, but two enterprise accounts (11% of ARR) renew in Q4 without expansion plans. Losing one would pull NRR to ~111%.',
'forecast q4 margin': 'If the Pro mix holds at 44% and hosting stays flat, Q4 gross margin lands at <b>62.0–62.6%</b>.'
};
const set = (open) => { split.toggleAttribute('data-closed', !open); ask.setAttribute('aria-expanded', String(open)); if (open) setTimeout(() => input.focus(), 360); else ask.focus(); };
const bottom = () => { thread.scrollTop = thread.scrollHeight; };
const stream = (bubble, html) => {
if (calm) { bubble.innerHTML = '<p>' + html + '</p>'; bottom(); return; }
const words = html.split(' '); let i = 0; bubble.innerHTML = '<p></p>'; const p = bubble.firstChild;
const t = setInterval(() => { p.innerHTML = words.slice(0, ++i).join(' '); bottom(); if (i >= words.length) clearInterval(t); }, 45);
};
const reply = (text) => {
const a = document.createElement('div'); a.className = 'a';
a.innerHTML = '<span class="flex min-h-5 items-center gap-1.25 *:size-1.5 *:rounded-full *:bg-tdl13-ai *:animate-tdl13-think" aria-hidden="true"><i></i><i style="animation-delay:.15s"></i><i style="animation-delay:.3s"></i></span>';
thread.appendChild(a); bottom();
setTimeout(() => stream(a, answers[text.toLowerCase()] || 'Good question — based on this board, I would start with the margin bridge: hosting savings and tier mix explain most of the movement.'), 900);
};
const send = (text) => {
text = text.trim(); if (!text) return;
const q = document.createElement('p'); q.className = 'q'; q.textContent = text; thread.appendChild(q);
input.value = ''; bottom(); reply(text);
};
const pending = thread.querySelector('[data-pending]');
if (pending) setTimeout(() => stream(pending, pending.dataset.pending), 1400);
ask.addEventListener('click', () => set(split.hasAttribute('data-closed')));
split.querySelector('[data-close]').addEventListener('click', () => set(false));
split.querySelector('[data-form]').addEventListener('submit', (e) => { e.preventDefault(); send(input.value); });
input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(input.value); } });
split.querySelectorAll('[data-chip]').forEach((c) => c.addEventListener('click', () => send(c.textContent)));
split.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !split.hasAttribute('data-closed')) set(false); });
document.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'j') { e.preventDefault(); set(split.hasAttribute('data-closed')); } });
});
})();
```How this works
Folding with the grid. The split is group/split; the script toggles data-closed. Because both states have two tracks, the browser tweens the second track from 21.25rem to 0 and the cards reflow every frame. The aside goes invisible after the fold (group-data-closed/split:delay-300) so its controls leave the tab order.
<div data-split class="group/split grid grid-cols-[minmax(0,1fr)_21.25rem]
transition-[grid-template-columns] duration-350
group-data-closed/split:grid-cols-[minmax(0,1fr)_0rem]">What stays CSS. The conic avatar spins an angle, and angles only interpolate once registered — @property --tdl13-spin is an at-rule, one of the few things utilities cannot write.
The conversation. A small, dependency-free script streams the pending reply into its bubble (respecting prefers-reduced-motion), appends new questions from the composer or chips, shows the typing dots, then answers from a canned map. The thread is an aria-live="polite" log.
Make it yours
- Panel width:
21.25remin the grid utility andw-85on the inner wrapper. - Swap the canned answers for a real model: replace
reply(text)with afetchto your endpoint. - AI gradient:
--color-tdl13-aiand--color-tdl13-ai2. - Start closed: add
data-closedto the split andaria-expanded="false"to the Copilot button.
Gotchas — read before shipping
- Animating to
0remneeds matching units;0orautowill snap instead of tween. - The inner wrapper keeps a fixed width while the track shrinks, so text never rewraps mid-animation — the aside clips it with
overflow-hidden. - Gradient borders via background-clip need a transparent border;
border-transparentis part of the recipe.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 123+ | 26+ | not yet | 123+ |
Floor set by oklch(), @property, @container, field-sizing as this demo is written. The core technique itself goes back further — Chrome 111+.
Tailwind v4 floor. field-sizing is Chromium-only today; elsewhere the composer stays one line tall (still usable).