Tabs Block (Astro)
contentbit/astro-tabsFreeComponent
Tabbed switcher for alternative methods.
Install it
npx shadcn@latest add https://contentbit.dev/r/astro-tabs.jsonSource
1---2import type { TabData, TabsData } from '@contentbit/blocks'3import type { ValidatedBlockNode } from '@contentbit/core'4import type { AstroBlockContext } from '@contentbit/astro'56import { isValidatedBlock } from '@contentbit/core'78interface Props {9 node: ValidatedBlockNode<unknown>10 ctx: AstroBlockContext11}1213const { node, ctx } = Astro.props14const data = node.data as TabsData15// The source offset alone collides when two documents render on one page, so16// a per-render suffix keeps tab/panel ids (and their ARIA wiring) unique.17const id = `cb-tabs-${node.position.start.offset}-${Math.random().toString(36).slice(2, 8)}`18const tabs = await Promise.all(19 data.blocks.map(async (tab, i) => ({20 title: String(tab.props.title),21 html: await ctx.renderMarkdown(isValidatedBlock(tab) ? (tab.data as TabData).markdown : tab.body),22 selected: i === 0,23 })),24)25---2627<div data-cb-styled data-cb-tabs class="my-6">28 <div role="tablist" class="bg-muted text-muted-foreground inline-flex h-9 items-center justify-center rounded-lg p-1">29 {30 tabs.map((tab, i) => (31 <button32 type="button"33 role="tab"34 id={`${id}-tab-${i}`}35 aria-controls={`${id}-panel-${i}`}36 aria-selected={tab.selected ? 'true' : 'false'}37 tabindex={tab.selected ? 0 : -1}38 class="aria-selected:bg-background aria-selected:text-foreground inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap transition-all aria-selected:shadow-sm"39 >40 {tab.title}41 </button>42 ))43 }44 </div>45 {46 tabs.map((tab, i) => (47 <div48 role="tabpanel"49 id={`${id}-panel-${i}`}50 aria-labelledby={`${id}-tab-${i}`}51 hidden={!tab.selected}52 class="mt-2 text-sm leading-relaxed"53 set:html={tab.html}54 />55 ))56 }57</div>5859<script>60 // One handler per page; activates a tab within its [data-cb-tabs] container.61 function activate(container: Element, index: number): void {62 const tabs = container.querySelectorAll<HTMLButtonElement>('[role="tab"]')63 const panels = container.querySelectorAll<HTMLElement>('[role="tabpanel"]')64 tabs.forEach((tab, i) => {65 tab.setAttribute('aria-selected', i === index ? 'true' : 'false')66 tab.tabIndex = i === index ? 0 : -167 })68 panels.forEach((panel, i) => {69 panel.hidden = i !== index70 })71 tabs[index]?.focus()72 }7374 function init(): void {75// … truncated
What it pulls in
npm packages
Files it writes
More from contentbit
All 20 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Callout Block (Astro)astro-callout | contentbit | Components | Free | 3 deps | |
| Comparison Block (Astro)astro-comparison | contentbit | Components | Free | 3 deps | |
| Content Renderer (Astro)astro-content-renderer | contentbit | Components | Free | 3 deps | |
| FAQ Block (Astro)astro-faq | contentbit | Components | Free | 3 deps | |
| Key Metrics Block (Astro)astro-key-metrics | contentbit | Components | Free | 3 deps | |
| Pros & Cons Block (Astro)astro-pros-cons | contentbit | Components | Free | 3 deps | |
| Quick Reference Block (Astro)astro-quick-ref | contentbit | Components | Free | 3 deps | |
| Steps Block (Astro)astro-steps | contentbit | Components | Free | 3 deps |
