Accordion
A vertically stacked set of collapsible sections, single or multiple open at a time.
Basic Accordion (Single)
Only one item can be open at a time. DefaultValue seeds the initial open item.
Each trigger gets aria-expanded and aria-controls automatically.
bit Bheadless is a headless component library for Blazor.
Behaviour and accessibility are built in; styling is entirely up to you.
You own the look and feel entirely. ARIA and keyboard support never have to be
re-implemented.
Use
data-state="open"|"closed" on any element, or just apply
classes. The library emits no styles of its own.
<BitBhAccordion DefaultValue="item-1" class="accordion">
<BitBhAccordionItem Value="item-1" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
What is bit Bheadless?
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">
bit Bheadless is a headless component library for Blazor.
Behaviour and accessibility are built in; styling is entirely up to you.
</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
<BitBhAccordionItem Value="item-2" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
Why headless?
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">
You own the look and feel entirely. ARIA and keyboard support never have to be
re-implemented.
</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
<BitBhAccordionItem Value="item-3" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
How do I style it?
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">
Use <code>data-state="open"|"closed"</code> on any element, or just apply
classes. The library emits no styles of its own.
</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
</BitBhAccordion>/* All styles are consumer-authored — the library ships zero CSS */
.accordion {
width: 100%;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
overflow: hidden;
}
.accordion-item { border-bottom: 1px solid #e4e4e7; }
.accordion-item:last-child { border-bottom: none; }
.accordion-trigger {
all: unset;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 1rem 1.25rem;
font-weight: 500;
cursor: pointer;
box-sizing: border-box;
}
.accordion-trigger:hover { background: #f4f4f5; }
.accordion-trigger:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; }
.accordion-trigger[data-state="open"] { background: #f4f4f5; }
.accordion-trigger[data-disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
/* Rotating chevron driven by BitBhAccordionTriggerContext.IsOpen */
.accordion-chevron { transition: transform 200ms ease; color: #71717a; }
.accordion-chevron.open { transform: rotate(180deg); }
.accordion-body {
padding: 0.875rem 1.25rem 1.125rem;
color: #71717a;
border-top: 1px solid #e4e4e7;
}Multiple Open Items
Set Type="BitBhAccordionType.Multiple" to allow any number of panels open
simultaneously.
Yes — every panel is independent. Try opening both!
No — both can be open at once.
<BitBhAccordion Type="BitBhAccordionType.Multiple" class="accordion">
<BitBhAccordionItem Value="faq-1" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
Can I open multiple panels?
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">
Yes — every panel is independent. Try opening both!
</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
<BitBhAccordionItem Value="faq-2" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
Does this one close when I open another?
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">
No — both can be open at once.
</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
</BitBhAccordion>/* All styles are consumer-authored — the library ships zero CSS */
.accordion {
width: 100%;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
overflow: hidden;
}
.accordion-item { border-bottom: 1px solid #e4e4e7; }
.accordion-item:last-child { border-bottom: none; }
.accordion-trigger {
all: unset;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 1rem 1.25rem;
font-weight: 500;
cursor: pointer;
box-sizing: border-box;
}
.accordion-trigger:hover { background: #f4f4f5; }
.accordion-trigger:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; }
.accordion-trigger[data-state="open"] { background: #f4f4f5; }
.accordion-trigger[data-disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
/* Rotating chevron driven by BitBhAccordionTriggerContext.IsOpen */
.accordion-chevron { transition: transform 200ms ease; color: #71717a; }
.accordion-chevron.open { transform: rotate(180deg); }
.accordion-body {
padding: 0.875rem 1.25rem 1.125rem;
color: #71717a;
border-top: 1px solid #e4e4e7;
}Disabled Item
Individual items can be disabled via Disabled="true". The whole accordion can
also be disabled at the root.
This item works normally.
You should not be able to open this.
<BitBhAccordion class="accordion">
<BitBhAccordionItem Value="d-1" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
I am enabled
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">This item works normally.</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
<BitBhAccordionItem Value="d-2" Disabled="true" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
I am disabled
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">You should not be able to open this.</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
</BitBhAccordion>/* All styles are consumer-authored — the library ships zero CSS */
.accordion {
width: 100%;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
overflow: hidden;
}
.accordion-item { border-bottom: 1px solid #e4e4e7; }
.accordion-item:last-child { border-bottom: none; }
.accordion-trigger {
all: unset;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 1rem 1.25rem;
font-weight: 500;
cursor: pointer;
box-sizing: border-box;
}
.accordion-trigger:hover { background: #f4f4f5; }
.accordion-trigger:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; }
.accordion-trigger[data-state="open"] { background: #f4f4f5; }
.accordion-trigger[data-disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
/* Rotating chevron driven by BitBhAccordionTriggerContext.IsOpen */
.accordion-chevron { transition: transform 200ms ease; color: #71717a; }
.accordion-chevron.open { transform: rotate(180deg); }
.accordion-body {
padding: 0.875rem 1.25rem 1.125rem;
color: #71717a;
border-top: 1px solid #e4e4e7;
}
/* BitBhAccordionItem itself also receives data-disabled — useful for striping
the row, dimming icons, etc. */
.accordion-item[data-disabled] {
background: #fafafa;
}Controlled Accordion
Drive the open state externally with Value + OnValueChange. The
button below toggles "Panel 2" programmatically.
Open: none
Controlled by external state.
This panel is toggled by the button above.
<div style="display:flex; flex-direction:column; gap:1rem; width:100%">
<div class="demo-row">
<BitBhButton OnClick="ToggleControlledItem" class="btn">
Toggle "Panel 2"
</BitBhButton>
<span class="demo-output">Open: @(controlledValue ?? "none")</span>
</div>
<BitBhAccordion Value="@controlledValue"
OnValueChange="v => controlledValue = v"
class="accordion">
<BitBhAccordionItem Value="ctrl-1" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
Panel 1
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">Controlled by external state.</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
<BitBhAccordionItem Value="ctrl-2" class="accordion-item">
<BitBhAccordionTrigger class="accordion-trigger">
<Template Context="t">
Panel 2
<span class="accordion-chevron @(t.IsOpen ? "open" : "")">▾</span>
</Template>
</BitBhAccordionTrigger>
<BitBhAccordionContent class="accordion-content">
<div class="accordion-body">This panel is toggled by the button above.</div>
</BitBhAccordionContent>
</BitBhAccordionItem>
</BitBhAccordion>
</div>
@code {
private string? controlledValue;
private void ToggleControlledItem()
{
controlledValue = controlledValue == "ctrl-2" ? null : "ctrl-2";
}
}/* All styles are consumer-authored — the library ships zero CSS */
.accordion {
width: 100%;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
overflow: hidden;
}
.accordion-item { border-bottom: 1px solid #e4e4e7; }
.accordion-item:last-child { border-bottom: none; }
.accordion-trigger {
all: unset;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 1rem 1.25rem;
font-weight: 500;
cursor: pointer;
box-sizing: border-box;
}
.accordion-trigger:hover { background: #f4f4f5; }
.accordion-trigger:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; }
.accordion-trigger[data-state="open"] { background: #f4f4f5; }
.accordion-trigger[data-disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
/* Rotating chevron driven by BitBhAccordionTriggerContext.IsOpen */
.accordion-chevron { transition: transform 200ms ease; color: #71717a; }
.accordion-chevron.open { transform: rotate(180deg); }
.accordion-body {
padding: 0.875rem 1.25rem 1.125rem;
color: #71717a;
border-top: 1px solid #e4e4e7;
}
/* Toggle button + status output used to drive the controlled accordion */
.demo-row {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
.demo-output {
font-size: 0.875rem;
color: #71717a;
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
font-family: inherit;
line-height: 1.25rem;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
background: #fff;
color: #18181b;
cursor: pointer;
transition: background 150ms ease, border-color 150ms ease;
}
.btn:hover { background: #f4f4f5; border-color: #d4d4d8; }
.btn:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }