CompositionBar
Stacked-Bar mit Legend zur Visualisierung von Aggregat-Kompositionen.
Playground
| Share | Value | Percent |
|---|---|---|
| Anteil A | 60 | 60.0 % |
| Anteil B | 30 | 30.0 % |
| Anteil C | 10 | 10.0 % |
<script lang="ts">
import { CompositionBar } from '@urbicon-ui/blocks';
const items = [
{ label: 'Anteil A', value: 60, intent: 'primary' },
{ label: 'Anteil B', value: 30, intent: 'success' },
{ label: 'Anteil C', value: 10, intent: 'warning' }
];
</script>
<CompositionBar
{items}
/>01 Examples
Heating Cost Pot
Stacked bar as a cost-pot composition: gas + heat pump electricity + maintenance + chimney sweep add up to the heating cost pot of a service charge statement.
Heating cost pot 2024 3.822,42 €
| Share | Value | Percent |
|---|---|---|
| Gas bill | 2.206,09 € | 57.7 % |
| Heat pump electricity | 1.127,31 € | 29.5 % |
| Heating system maintenance | 361,02 € | 9.4 % |
| Chimney sweep levy | 128,00 € | 3.3 % |
<div class="w-full max-w-xl">
<CompositionBar
items={heatingItems}
formatValue={formatEur}
size="lg"
legendPlacement="bottom"
showTotal
totalLabel="Heating cost pot 2024"
/>
</div>Tiny Segments
Very small segments are raised to a minimum width so hover and tooltip still work on them.
| Share | Value | Percent |
|---|---|---|
| Main share | 950 | 98.3 % |
| Mini A | 8 | 0.8 % |
| Mini B | 3 | 0.3 % |
| Mini C | 5 | 0.5 % |
<div class="w-full max-w-xl">
<CompositionBar items={tinyItems} formatValue={(v) => `${v}`} size="lg" />
</div>Total Override (Remainder)
If the explicit total exceeds Σ items.value, the unaccounted share is rendered as a dashed remainder area.
Expected 5.000,00 € 5.000,00 €
| Share | Value | Percent |
|---|---|---|
| Gas bill | 2.206,09 € | 44.1 % |
| Heat pump electricity | 1.127,31 € | 22.5 % |
| Heating system maintenance | 361,02 € | 7.2 % |
| Chimney sweep levy | 128,00 € | 2.6 % |
| Expected 5.000,00 € (remaining) | 1.177,58 € | 23.6 % |
<div class="w-full max-w-xl">
<CompositionBar
items={heatingItems}
total={500000}
formatValue={formatEur}
size="lg"
showTotal
totalLabel="Expected 5.000,00 €"
/>
</div>Raw Color Overrides
Per-item color prop for brand-specific palettes (e.g. token allocations).
| Share | Value | Percent |
|---|---|---|
| Treasury | 40 % | 40 % |
| Liquidity | 30 % | 30 % |
| Team | 15 % | 15 % |
| Community | 15 % | 15 % |
<div class="w-full max-w-xl">
<CompositionBar
items={tokenItems}
formatValue={(v) => `${v} %`}
formatPercent={(p) => `${Math.round(p)} %`}
size="lg"
legendPlacement="bottom"
/>
</div>02 Customization
Custom Tooltip Snippet
Custom tooltip content with additional metadata via the tooltip snippet.
| Share | Value | Percent |
|---|---|---|
| Gas bill | 2.206,09 € | 57.7 % |
| Heat pump electricity | 1.127,31 € | 29.5 % |
| Heating system maintenance | 361,02 € | 9.4 % |
| Chimney sweep levy | 128,00 € | 3.3 % |
<div class="w-full max-w-xl">
<CompositionBar items={heatingItems} formatValue={formatEur} size="lg">
{#snippet tooltip(item, percent)}
<span class="block font-medium">{item.label}</span>
<span class="text-text-tertiary text-2xs block tabular-nums">
{formatEur(item.value)} · {Math.round(percent)} %
</span>
{#if item.intent === 'warning'}
<span class="text-warning text-3xs mt-1 block tracking-wide uppercase">
statutory levy
</span>
{/if}
{/snippet}
</CompositionBar>
</div>Compact Card Embedding
Embedded in a card with a header at the sm size.
Treasury Allocation
Q1 2026| Share | Value | Percent |
|---|---|---|
| Treasury | 40 % | 40 % |
| Liquidity | 30 % | 30 % |
| Team | 15 % | 15 % |
| Community | 15 % | 15 % |
<div class="border-border-subtle bg-surface-elevated w-full max-w-md rounded-2xl border p-5">
<header class="mb-3 flex items-baseline justify-between">
<h3 class="text-text-primary text-sm font-semibold">Treasury Allocation</h3>
<span class="text-text-tertiary text-xs">Q1 2026</span>
</header>
<CompositionBar
items={tokenItems}
formatValue={(v) => `${v} %`}
formatPercent={(p) => `${Math.round(p)} %`}
size="sm"
legendPlacement="bottom"
/>
</div>03 Accessibility
ARIA Image Role + Summary
The bar container has role="img" with a combined aria-label summary (total plus each share with value and
percent), so screen readers can grasp the bar as a whole.
Table Fallback
An sr-only table duplicates the data in tabular form (share / value / percent) for screen readers that prefer tables over images.
Keyboard Navigation
Tab focuses the next segment, Arrow keys move between segments, Home /End jump to the first/last, Enter/Space triggers onItemSelect.
Bidirectional Highlight
Hovering or focusing a bar segment dims all other segments AND highlights the matching
legend entry (and vice versa) — the link runs through the shared item.id (fallback: index).
04 API Reference
23 props 1 required
Prop | Type | Default | Description | |
|---|---|---|---|---|
items required | CompositionItem[] | — | Composition shares in the order they should appear in the bar. | |
class | string | — | Extra classes merged onto the wrapper. | |
formatPercent | (percent: number) => string | — | Format function for percentages. Default: 1 fraction digit, formatted with the active
locale (e.g. 12.3 %). | |
formatValue | (value: number) => string | — | Format function for values (tooltip, legend, total). | |
intent | CompositionBarIntent | 'primary' | Default intent for items without their own intent/color. | |
legendItem | Snippet<[item: CompositionItem, percent: number]> | — | Custom rendering of a legend entry. Receives the item and its percentage. Fully replaces the default rendering (dot + label + value/percent). | |
legendPlacement variant | bottomleftrighttop | bottom | Controls the legendPlacement behavior and appearance of the CompositionBar component. Available options: bottom, left, right, top. | |
minSegmentPercent | number | 1.5 | Minimum segment width in percent. Values below it are raised to this width so they stay visible in the bar (nothing gets swallowed). Larger segments are shrunk proportionally so the sum still adds up to 100%. | |
onItemSelect | (item: CompositionItem, index: number) => void | — | Selection callback fired when a bar segment or legend entry is clicked. Receives the item and its index. Makes the bar interactive (cursor + click handler). | |
orientation variant | horizontalvertical | horizontal | Controls the orientation behavior and appearance of the CompositionBar component. Available options: horizontal, vertical. | |
preset | string | — | Preset name (registered via <BlocksProvider presets={{ CompositionBar: {...} }}>). | |
showLegend | boolean | true | Show the legend. Position it with legendPlacement. | |
showPercentages | boolean | true | Show percentages in legend and tooltip. | |
showTotal | boolean | false | Render the total value after the legend. | |
showValues | boolean | false | Render the value directly inside the bar segment (in addition to the legend). Narrow segments are skipped automatically to prevent overflow — only segments with sufficient width (≥ 8% or ~40 px) show their value. Useful for print, PDF attachments, or dense dashboards where hover tooltips are not enough. | |
size variant | lgmdsm | md | Controls the dimensions, padding, and text size of the CompositionBar. Affects the component's physical footprint. Available options: lg, md, sm. | |
slotClasses | Partial<Record<CompositionBarSlots, string>> | — | Per-slot class overrides. | |
tooltip | Snippet<[item: CompositionItem, percent: number]> | — | Custom tooltip content. Replaces the default layout (label, value, percent). | |
total | number | — | Optional fixed total value. Default: Σ items.value. If the explicit total exceeds the sum, the remaining area is rendered neutrally ("unaccounted share"). If it is below the sum, segments are scaled to 100% and a console warning is emitted. | |
totalLabel | string | — | Label rendered before the total. Defaults to the localized "Total" label from the blocks i18n bundle. | |
unstyled | boolean | — | Remove all default classes; only the layout structure remains. | |
...CompositionBarVariants variant | VariantProps | — | Styling variants from CompositionBarVariants | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
05 Types
Local type definitions used by this component.
7 types
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
CompositionBarIntent | type | helper | 1 | — | |
CompositionItem | interface | helper | 1 | A single share in the stacked bar. | |
CompositionBarProps | interface | props | 0 | — | |
CompositionBarSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
CompositionBarVariants | type | variant | 0 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: | |
VariantProps | type | helper | 1 | — |
06 Installation
Import
import { CompositionBar } from '@urbicon-ui/blocks';