Components · Navigation
Tabs
Panels behind a row of tabs, or page links that look like tabs. Arrow keys, Home and End.
38% of the 2,062 sites in The UI Index have a navigation (781 sites).
Three pages changed since the last deploy.
Twelve events this week, most of them builds.
Name, domain and who can see the project.
Billing is managed by the workspace owner.
Today: 4 deploys.
This week: 23 deploys.
This month: 81 deploys.
Three pages changed since the last deploy.
Twelve events this week, most of them builds.
Name, domain and who can see the project.
Billing is managed by the workspace owner.
Today: 4 deploys.
This week: 23 deploys.
This month: 81 deploys.
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ tabs.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ tabs.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS and the script.
import { useState, type KeyboardEvent, type ReactNode } from 'react';
import { cn } from '@/lib/utils';
import { countClasses, panelId, tabId, tabListVariants, tabPanelClasses, tabVariants, type TabItem, type TabsVariants } from './tabs.variants';
export type { TabItem };
export interface TabsProps extends TabsVariants {
tabs: TabItem[];
/** Id of the selected tab (the current page in nav mode). Default: the first enabled tab. */
selected?: string;
/** The accessible name of the tab list. */
label: string;
/** Unique on the page; prefixes the element ids. */
id: string;
/** Panel content per tab id (the Astro version's named slots). */
panels?: Record<string, ReactNode>;
onTabsChange?: (detail: { id: string }) => void;
className?: string;
}
/** WAI-ARIA tabs with automatic activation: click, Left/Right (wrapping, disabled skipped), Home/End. */
export function Tabs({ tabs, selected, label, id, variant, panels = {}, onTabsChange, className }: TabsProps) {
const initial = selected ?? tabs.find((t) => !t.disabled)?.id;
const [current, setCurrent] = useState(initial);
const [prev, setPrev] = useState(selected);
if (selected !== prev) {
setPrev(selected);
setCurrent(initial);
}
const isNav = tabs.length > 0 && tabs.every((t) => t.href);
if (isNav)
return (
<nav aria-label={label} className={cn('w-full', className)}>
<ul className={cn(tabListVariants({ variant }), 'm-0 list-none p-0')}>
{tabs.map((t) => (
<li key={t.id} className="shrink-0">
<a href={t.disabled ? undefined : t.href} aria-disabled={t.disabled ? 'true' : undefined} role={t.disabled ? 'link' : undefined} aria-current={t.id === current ? 'page' : undefined} className={tabVariants({ variant })}>
{t.label}
{t.count !== undefined && <span className={countClasses}>{t.count}</span>}
</a>
</li>
))}
</ul>
</nav>
);
const select = (t: TabItem, focus: boolean) => {
setCurrent(t.id);
const el = document.getElementById(tabId(id, t.id));
if (focus) el?.focus();
el?.closest('[data-tabs]')?.dispatchEvent(new CustomEvent('tabs-change', { bubbles: true, detail: { id: tabId(id, t.id) } }));
onTabsChange?.({ id: tabId(id, t.id) });
};
const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>, t: TabItem) => {
const enabled = tabs.filter((x) => !x.disabled);
const i = enabled.indexOf(t);
const next =
e.key === 'ArrowRight' ? enabled[(i + 1) % enabled.length]
: e.key === 'ArrowLeft' ? enabled[(i - 1 + enabled.length) % enabled.length]
: e.key === 'Home' ? enabled[0]
: e.key === 'End' ? enabled.at(-1)
: undefined;
if (!next) return;
e.preventDefault();
select(next, true);
};
return (
<div data-tabs="" className={cn('w-full', className)}>
<div role="tablist" aria-label={label} className={tabListVariants({ variant })}>
{tabs.map((t) => (
<button key={t.id} type="button" role="tab" id={tabId(id, t.id)} aria-controls={panelId(id, t.id)} aria-selected={t.id === current ? 'true' : 'false'} tabIndex={t.id === current ? 0 : -1} disabled={t.disabled} className={tabVariants({ variant })} onClick={() => select(t, false)} onKeyDown={(e) => onKeyDown(e, t)}>
{t.label}
{t.count !== undefined && <span className={countClasses}>{t.count}</span>}
</button>
))}
</div>
{tabs.map((t) => (
<div key={t.id} role="tabpanel" id={panelId(id, t.id)} aria-labelledby={tabId(id, t.id)} hidden={t.id !== current} tabIndex={0} className={tabPanelClasses}>
{panels[t.id]}
</div>
))}
</div>
);
}import { cva, type VariantProps } from 'class-variance-authority';
/** The row of tabs. line: a hairline under the row, the selected tab underlined in ink. pill: a muted track, the
* selected tab a raised chip. */
export const tabListVariants = cva('flex max-w-full items-center overflow-x-auto [scrollbar-width:none]', {
variants: {
variant: {
// contain: inline-size keeps the row from widening an auto-sized parent (a grid track would grow to fit every
// tab); it fills its space and scrolls instead. The edges shade where more tabs are hidden (scrolling shadows:
// the covers scroll with the tabs, the shades stay; no script). --tabs-surface: the ground under the row; the shade turns light in dark mode.
line: 'w-full gap-1 border-b [contain:inline-size] [background:linear-gradient(to_right,var(--tabs-surface,var(--background))_50%,transparent)_left/2rem_100%_no-repeat_local,linear-gradient(to_left,var(--tabs-surface,var(--background))_50%,transparent)_right/2rem_100%_no-repeat_local,radial-gradient(farthest-side_at_0_50%,var(--tabs-shade,rgb(0_0_0/0.22)),transparent)_left/0.75rem_100%_no-repeat_scroll,radial-gradient(farthest-side_at_100%_50%,var(--tabs-shade,rgb(0_0_0/0.22)),transparent)_right/0.75rem_100%_no-repeat_scroll]',
pill: 'inline-flex w-fit gap-1 rounded-lg bg-muted p-1',
},
},
defaultVariants: { variant: 'line' },
});
/** One tab. Selected comes from aria-selected (or aria-current in nav mode), so the state is the DOM's, not a class. */
export const tabVariants = cva(
'relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
// The underline grows from the centre when a tab is selected (no movement with reduced motion).
// The ring sits inside the tab: the row scrolls (overflow-x), which would clip a ring drawn outside it.
line: 'h-10 px-3 outline-offset-[-6px] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:scale-x-0 after:rounded-full after:bg-foreground after:transition-transform after:duration-200 hover:bg-accent/60 active:bg-pressed aria-selected:text-foreground aria-selected:after:scale-x-100 aria-[current=page]:text-foreground aria-[current=page]:after:scale-x-100 rounded-md',
pill: 'h-8 rounded-md px-3 hover:bg-input/45 hover:text-foreground active:scale-[0.97] active:bg-pressed aria-selected:hover:shadow-[0_1px_3px_rgb(0_0_0/0.14),0_0_0_1px_var(--input)] aria-selected:active:bg-background/70 aria-selected:bg-background aria-selected:text-foreground aria-selected:shadow-[0_1px_2px_rgb(0_0_0/0.08),0_0_0_1px_var(--border)] aria-[current=page]:bg-background aria-[current=page]:text-foreground',
},
},
defaultVariants: { variant: 'line' },
},
);
/** A panel. It takes focus (tabindex 0) so its content is one Tab stop away from the list. */
/** A panel. It takes focus (tabindex 0); its content is inset 12px from the ring, with no shift in layout. */
export const tabPanelClasses = 'mt-4 -mx-3 rounded-md px-3';
export type TabsVariants = VariantProps<typeof tabListVariants>;
export interface TabItem {
/** Unique within the tabs; the panel's slot name (Astro) or key in `panels` (React). */
id: string;
label: string;
/** A count after the label, muted. */
count?: number;
disabled?: boolean;
/** Makes the tab a page link. When every tab has an href the tabs render as a <nav> with aria-current. */
href?: string;
}
export const tabId = (id: string, tab: string) => `${id}-tab-${tab}`;
export const panelId = (id: string, tab: string) => `${id}-panel-${tab}`;
export const countClasses = 'font-normal text-muted-foreground tabular-nums';---
import { cn } from '@/lib/utils';
import { countClasses, panelId, tabId, tabListVariants, tabPanelClasses, tabVariants, type TabItem, type TabsVariants } from './tabs.variants';
interface Props extends TabsVariants {
tabs: TabItem[];
/** Id of the selected tab (the current page in nav mode). Default: the first enabled tab. */
selected?: string;
/** The accessible name of the tab list. */
label: string;
/** Unique on the page; prefixes the element ids. */
id: string;
class?: string;
}
const { tabs, selected, label, id, variant, class: className } = Astro.props;
const current = selected ?? tabs.find((t) => !t.disabled)?.id;
const isNav = tabs.length > 0 && tabs.every((t) => t.href);
---
{
isNav ? (
<nav aria-label={label} class={cn('w-full', className)}>
<ul class={cn(tabListVariants({ variant }), 'm-0 list-none p-0')}>
{tabs.map((t) => (
<li class="shrink-0">
<a href={t.disabled ? undefined : t.href} aria-disabled={t.disabled ? 'true' : undefined} role={t.disabled ? 'link' : undefined} aria-current={t.id === current ? 'page' : undefined} class={tabVariants({ variant })}>
{t.label}
{t.count !== undefined && <span class={countClasses}>{t.count}</span>}
</a>
</li>
))}
</ul>
</nav>
) : (
<div data-tabs class={cn('w-full', className)}>
<div role="tablist" aria-label={label} class={tabListVariants({ variant })}>
{tabs.map((t) => (
<button type="button" role="tab" id={tabId(id, t.id)} aria-controls={panelId(id, t.id)} aria-selected={t.id === current ? 'true' : 'false'} tabindex={t.id === current ? 0 : -1} disabled={t.disabled} class={tabVariants({ variant })}>
{t.label}
{t.count !== undefined && <span class={countClasses}>{t.count}</span>}
</button>
))}
</div>
{tabs.map((t) => (
<div role="tabpanel" id={panelId(id, t.id)} aria-labelledby={tabId(id, t.id)} hidden={t.id !== current} tabindex="0" class={tabPanelClasses}>
<slot name={t.id} />
</div>
))}
</div>
)
}
<script>
import './tabs.client';
</script>// Tabs behaviour for Astro and plain HTML (the React version keeps its own state). WAI-ARIA tabs with automatic
// activation: click, Left/Right (wrapping, disabled tabs skipped), Home/End. Delegated on document, so tabs added
// later work too. Selecting dispatches a bubbling `tabs-change` event ({ id: <tab element id> }) from [data-tabs].
declare global {
interface Window {
__caseroomTabs?: boolean;
}
}
function select(list: HTMLElement, next: HTMLElement, focus: boolean) {
const root = list.closest<HTMLElement>('[data-tabs]');
if (!root) return;
for (const t of list.querySelectorAll<HTMLElement>('[role=tab]')) {
const on = t === next;
// Only what changes is written, so pressing the current tab again leaves the DOM alone.
if (t.getAttribute('aria-selected') !== String(on)) t.setAttribute('aria-selected', String(on));
if (t.tabIndex !== (on ? 0 : -1)) t.tabIndex = on ? 0 : -1;
const panel = document.getElementById(t.getAttribute('aria-controls') ?? '');
if (panel && panel.hidden !== !on) panel.hidden = !on;
}
if (focus) next.focus();
root.dispatchEvent(new CustomEvent('tabs-change', { bubbles: true, detail: { id: next.id } }));
}
if (typeof window !== 'undefined' && !window.__caseroomTabs) {
window.__caseroomTabs = true;
document.addEventListener('click', (e) => {
const t = (e.target as Element).closest<HTMLButtonElement>('[data-tabs] [role=tab]');
if (t && !t.disabled && t.parentElement) select(t.parentElement, t, false);
});
document.addEventListener('keydown', (e) => {
const t = (e.target as Element).closest<HTMLButtonElement>('[data-tabs] [role=tab]');
if (!t?.parentElement) return;
const list = t.parentElement;
const tabs = [...list.querySelectorAll<HTMLButtonElement>('[role=tab]')].filter((x) => !x.disabled);
const i = tabs.indexOf(t);
const next =
e.key === 'ArrowRight' ? tabs[(i + 1) % tabs.length]
: e.key === 'ArrowLeft' ? tabs[(i - 1 + tabs.length) % tabs.length]
: e.key === 'Home' ? tabs[0]
: e.key === 'End' ? tabs.at(-1)
: undefined;
if (!next) return;
e.preventDefault();
select(list, next, true);
});
}
export {};import { cva, type VariantProps } from 'class-variance-authority';
/** The row of tabs. line: a hairline under the row, the selected tab underlined in ink. pill: a muted track, the
* selected tab a raised chip. */
export const tabListVariants = cva('flex max-w-full items-center overflow-x-auto [scrollbar-width:none]', {
variants: {
variant: {
// contain: inline-size keeps the row from widening an auto-sized parent (a grid track would grow to fit every
// tab); it fills its space and scrolls instead. The edges shade where more tabs are hidden (scrolling shadows:
// the covers scroll with the tabs, the shades stay; no script). --tabs-surface: the ground under the row; the shade turns light in dark mode.
line: 'w-full gap-1 border-b [contain:inline-size] [background:linear-gradient(to_right,var(--tabs-surface,var(--background))_50%,transparent)_left/2rem_100%_no-repeat_local,linear-gradient(to_left,var(--tabs-surface,var(--background))_50%,transparent)_right/2rem_100%_no-repeat_local,radial-gradient(farthest-side_at_0_50%,var(--tabs-shade,rgb(0_0_0/0.22)),transparent)_left/0.75rem_100%_no-repeat_scroll,radial-gradient(farthest-side_at_100%_50%,var(--tabs-shade,rgb(0_0_0/0.22)),transparent)_right/0.75rem_100%_no-repeat_scroll]',
pill: 'inline-flex w-fit gap-1 rounded-lg bg-muted p-1',
},
},
defaultVariants: { variant: 'line' },
});
/** One tab. Selected comes from aria-selected (or aria-current in nav mode), so the state is the DOM's, not a class. */
export const tabVariants = cva(
'relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
// The underline grows from the centre when a tab is selected (no movement with reduced motion).
// The ring sits inside the tab: the row scrolls (overflow-x), which would clip a ring drawn outside it.
line: 'h-10 px-3 outline-offset-[-6px] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:scale-x-0 after:rounded-full after:bg-foreground after:transition-transform after:duration-200 hover:bg-accent/60 active:bg-pressed aria-selected:text-foreground aria-selected:after:scale-x-100 aria-[current=page]:text-foreground aria-[current=page]:after:scale-x-100 rounded-md',
pill: 'h-8 rounded-md px-3 hover:bg-input/45 hover:text-foreground active:scale-[0.97] active:bg-pressed aria-selected:hover:shadow-[0_1px_3px_rgb(0_0_0/0.14),0_0_0_1px_var(--input)] aria-selected:active:bg-background/70 aria-selected:bg-background aria-selected:text-foreground aria-selected:shadow-[0_1px_2px_rgb(0_0_0/0.08),0_0_0_1px_var(--border)] aria-[current=page]:bg-background aria-[current=page]:text-foreground',
},
},
defaultVariants: { variant: 'line' },
},
);
/** A panel. It takes focus (tabindex 0) so its content is one Tab stop away from the list. */
/** A panel. It takes focus (tabindex 0); its content is inset 12px from the ring, with no shift in layout. */
export const tabPanelClasses = 'mt-4 -mx-3 rounded-md px-3';
export type TabsVariants = VariantProps<typeof tabListVariants>;
export interface TabItem {
/** Unique within the tabs; the panel's slot name (Astro) or key in `panels` (React). */
id: string;
label: string;
/** A count after the label, muted. */
count?: number;
disabled?: boolean;
/** Makes the tab a page link. When every tab has an href the tabs render as a <nav> with aria-current. */
href?: string;
}
export const tabId = (id: string, tab: string) => `${id}-tab-${tab}`;
export const panelId = (id: string, tab: string) => `${id}-panel-${tab}`;
export const countClasses = 'font-normal text-muted-foreground tabular-nums';<div class="grid w-full max-w-lg gap-10">
<div data-tabs class="w-full">
<div role="tablist" aria-label="Project" class="flex max-w-full items-center overflow-x-auto [scrollbar-width:none] w-full gap-1 border-b [contain:inline-size] [background:linear-gradient(to_right,var(--tabs-surface,var(--background))_50%,transparent)_left/2rem_100%_no-repeat_local,linear-gradient(to_left,var(--tabs-surface,var(--background))_50%,transparent)_right/2rem_100%_no-repeat_local,radial-gradient(farthest-side_at_0_50%,var(--tabs-shade,rgb(0_0_0/0.22)),transparent)_left/0.75rem_100%_no-repeat_scroll,radial-gradient(farthest-side_at_100%_50%,var(--tabs-shade,rgb(0_0_0/0.22)),transparent)_right/0.75rem_100%_no-repeat_scroll]">
<button type="button" role="tab" id="ex-tabs-tab-overview" aria-controls="ex-tabs-panel-overview" aria-selected="true" tabindex="0" class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-3 outline-offset-[-6px] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:scale-x-0 after:rounded-full after:bg-foreground after:transition-transform after:duration-200 hover:bg-accent/60 active:bg-pressed aria-selected:text-foreground aria-selected:after:scale-x-100 aria-[current=page]:text-foreground aria-[current=page]:after:scale-x-100 rounded-md">
Overview
</button>
<button type="button" role="tab" id="ex-tabs-tab-activity" aria-controls="ex-tabs-panel-activity" aria-selected="false" tabindex="-1" class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-3 outline-offset-[-6px] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:scale-x-0 after:rounded-full after:bg-foreground after:transition-transform after:duration-200 hover:bg-accent/60 active:bg-pressed aria-selected:text-foreground aria-selected:after:scale-x-100 aria-[current=page]:text-foreground aria-[current=page]:after:scale-x-100 rounded-md">
Activity
<span class="font-normal text-muted-foreground tabular-nums">12</span>
</button>
<button type="button" role="tab" id="ex-tabs-tab-settings" aria-controls="ex-tabs-panel-settings" aria-selected="false" tabindex="-1" class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-3 outline-offset-[-6px] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:scale-x-0 after:rounded-full after:bg-foreground after:transition-transform after:duration-200 hover:bg-accent/60 active:bg-pressed aria-selected:text-foreground aria-selected:after:scale-x-100 aria-[current=page]:text-foreground aria-[current=page]:after:scale-x-100 rounded-md">
Settings
</button>
<button type="button" role="tab" id="ex-tabs-tab-billing" aria-controls="ex-tabs-panel-billing" aria-selected="false" tabindex="-1" disabled class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-10 px-3 outline-offset-[-6px] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:scale-x-0 after:rounded-full after:bg-foreground after:transition-transform after:duration-200 hover:bg-accent/60 active:bg-pressed aria-selected:text-foreground aria-selected:after:scale-x-100 aria-[current=page]:text-foreground aria-[current=page]:after:scale-x-100 rounded-md">
Billing
</button>
</div>
<div role="tabpanel" id="ex-tabs-panel-overview" aria-labelledby="ex-tabs-tab-overview" tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">Three pages changed since the last deploy.</p>
</div>
<div role="tabpanel" id="ex-tabs-panel-activity" aria-labelledby="ex-tabs-tab-activity" hidden tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">Twelve events this week, most of them builds.</p>
</div>
<div role="tabpanel" id="ex-tabs-panel-settings" aria-labelledby="ex-tabs-tab-settings" hidden tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">Name, domain and who can see the project.</p>
</div>
<div role="tabpanel" id="ex-tabs-panel-billing" aria-labelledby="ex-tabs-tab-billing" hidden tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">Billing is managed by the workspace owner.</p>
</div>
</div>
<div data-tabs class="w-full">
<div role="tablist" aria-label="View" class="flex max-w-full items-center overflow-x-auto [scrollbar-width:none] inline-flex w-fit gap-1 rounded-lg bg-muted p-1">
<button type="button" role="tab" id="ex-view-tab-day" aria-controls="ex-view-panel-day" aria-selected="false" tabindex="-1" class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-8 rounded-md px-3 hover:bg-input/45 hover:text-foreground active:scale-[0.97] active:bg-pressed aria-selected:hover:shadow-[0_1px_3px_rgb(0_0_0/0.14),0_0_0_1px_var(--input)] aria-selected:active:bg-background/70 aria-selected:bg-background aria-selected:text-foreground aria-selected:shadow-[0_1px_2px_rgb(0_0_0/0.08),0_0_0_1px_var(--border)] aria-[current=page]:bg-background aria-[current=page]:text-foreground">
Day
</button>
<button type="button" role="tab" id="ex-view-tab-week" aria-controls="ex-view-panel-week" aria-selected="true" tabindex="0" class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-8 rounded-md px-3 hover:bg-input/45 hover:text-foreground active:scale-[0.97] active:bg-pressed aria-selected:hover:shadow-[0_1px_3px_rgb(0_0_0/0.14),0_0_0_1px_var(--input)] aria-selected:active:bg-background/70 aria-selected:bg-background aria-selected:text-foreground aria-selected:shadow-[0_1px_2px_rgb(0_0_0/0.08),0_0_0_1px_var(--border)] aria-[current=page]:bg-background aria-[current=page]:text-foreground">
Week
</button>
<button type="button" role="tab" id="ex-view-tab-month" aria-controls="ex-view-panel-month" aria-selected="false" tabindex="-1" class="relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap text-muted-foreground transition-[color,background-color,box-shadow] select-none hover:text-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 h-8 rounded-md px-3 hover:bg-input/45 hover:text-foreground active:scale-[0.97] active:bg-pressed aria-selected:hover:shadow-[0_1px_3px_rgb(0_0_0/0.14),0_0_0_1px_var(--input)] aria-selected:active:bg-background/70 aria-selected:bg-background aria-selected:text-foreground aria-selected:shadow-[0_1px_2px_rgb(0_0_0/0.08),0_0_0_1px_var(--border)] aria-[current=page]:bg-background aria-[current=page]:text-foreground">
Month
</button>
</div>
<div role="tabpanel" id="ex-view-panel-day" aria-labelledby="ex-view-tab-day" hidden tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">Today: 4 deploys.</p>
</div>
<div role="tabpanel" id="ex-view-panel-week" aria-labelledby="ex-view-tab-week" tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">This week: 23 deploys.</p>
</div>
<div role="tabpanel" id="ex-view-panel-month" aria-labelledby="ex-view-tab-month" hidden tabindex="0" class="mt-4 -mx-3 rounded-md px-3">
<p class="text-sm text-muted-foreground">This month: 81 deploys.</p>
</div>
</div>
</div><script type="module" src="/caseroom/r/html/tabs.js"></script>