NbBlueprintCard is a node card designed to live inside an NbBlueprint canvas. It renders a card with an identity rail down its left edge, typed input/output ports, a category label, an optional enable toggle, and an optional remove button. It is presentational: the parent owns the card's position and the connections between cards.
Basic card
<template>
<NbBlueprintCard
id="filter"
title="Low-pass Filter"
category="effect"
color="#a855f7"
:ports="[
{ id: 'in', label: 'Audio', type: 'input' },
{ id: 'out', label: 'Audio', type: 'output' },
]"
/>
</template>Ports and connected state
Ports are split automatically by type: input on the left, output on the right. Pass connectedPorts with an array of port IDs to show which ports are wired; a wired pin is filled in its own colour.
Ports are the part of a node graph people look at most, so their anatomy, geometry and states are documented in full under Ports below.
Ports
A port is three elements, because it answers three different questions and each has a different right answer.
| Element | Is | Sized |
|---|---|---|
.nb-blueprint-card__port-hit | The <button>. The only thing that takes a click or a keypress. | 24×24, transparent |
.nb-blueprint-card__port | The pin. Carries data-port, so this box, and nothing else, is what the canvas measures to place a wire endpoint. | 8×16 by default |
.nb-blueprint-card__port-label | The optional inline label, a sibling of the pin, drawn inside the card. | Type only |
Keeping them apart is what lets the target be comfortable without the pin being drawn that large, and what keeps a label out of the box the wire layer measures.
Geometry
Pins sit outside the card. The hit target's inner edge lands exactly on the card's outer edge, so nothing in the port column overlaps the card: its border and its identity rail stay continuous, and a click just inside the card is never intercepted by a port. It is also what Unreal, Blender, n8n and Node-RED do, so it is what people arriving at a node graph already expect.
The column is anchored to the top of the card, not centred in its height:
| Custom property | Default | Is |
|---|---|---|
--nb-blueprint-port-top | 17px, or 26px with a category, 13px when compact | Centre of the first pin, from the card's top edge. |
--nb-blueprint-port-pitch | 24px | Centre-to-centre spacing, and the height of each hit target, so adjacent targets tile rather than overlap. |
--nb-blueprint-port-width | 8px | Pin width. |
--nb-blueprint-port-height | 16px | Pin height. |
--nb-blueprint-port-hit | 24px | Hit target width. |
Two things follow from anchoring to the top, and both matter more than they sound:
- A card's wires never move when its body changes. Expanding a card, adding a parameter row, or rendering different slot content leaves every pin exactly where it was.
- A straight chain draws straight wires. Two cards whose ports start at the same offset are joined by a horizontal wire regardless of their heights.
A card that has more ports than chrome grows to contain them: its min-height is derived from the pin count, so the last pin always sits inside the card with a corner's worth of clearance. The pitch is never compressed to make ports fit, because the pitch is also the hit target's height, and a 12-output card should look like a 12-output card.
To move the whole column, a card with a taller custom header, say, set --nb-blueprint-port-top on the card. Prefer that to changing the pitch, which is tied to the hit-target height.
States
| State | Looks like | Set by |
|---|---|---|
| Free | Outline on the card surface | The default |
| Hover | Filled in the pin colour | Pointer over the hit target |
| Focus | 2px focus ring around the pin | Keyboard focus on the hit target |
| Connected | Solid fill, no halo | connectedPorts |
| Live | Solid fill, plus a static halo in the signal colour | activePorts |
| Metered | Fills from the bottom in proportion to the level | portLevels |
| Metered + live | The meter, plus the halo | Both together |
| Valid target | Accent ring | Automatic, while a wire is being dragged |
| Invalid target | Dimmed to 30% | Automatic, while a wire is being dragged |
| Required | Heavier outline | required: true on an input port |
A connected pin is filled and carries no halo. The halo means signal, and a wired-but-silent port has none, so "this is wired" and "this is carrying something" stay tellable apart. The halo is static: the objection to the old treatment was that it looped forever on every live port, not that live ports were marked at all.
portLevels wins over activePorts for the pin's fill, so a port in both renders as a meter rather than a solid block, and takes the halo on top.
Drop targets
While a wire is being dragged, every pin in the canvas answers, in advance, whether it could accept it. Compatible pins take an accent ring; the rest dim. Nothing needs wiring up for this: NbBlueprint publishes the origin port through its card context and each card works out its own pins.
A pin can accept a wire when all of these hold:
- It is on a different card.
- It faces the other way, an output can only reach an input.
- Its
dataTypeis compatible with the origin's.
Compatibility is deliberately loose, because a graph editor that refuses plausible connections is worse than one that allows a few odd ones:
- An undeclared
dataType, or'any'at either end, connects to anything. - Identical types connect.
- Members of a family connect:
audio:monoreachesaudio:stereoandaudio:bus, because they share the segment before the colon.midireaches neither.
Collapsed cards
A collapsed card keeps its header: the chevron, the title, the status glyph and the toggle. Its width floor is derived from that chrome plus a readable amount of title, so the title is never the thing squeezed out to make room for the controls.
Its ports stay in the DOM, so wires can still resolve their endpoints, but the slots flatten onto a single combined pin per side. Every hidden pin sits exactly where that combined pin is drawn, so wires converge on the connection point the user can actually see.
Signal level and activity
Pass portLevels, a map of port id to a number from 0 to 1, and each of those pins becomes a meter, filling from the bottom in the pin colour. A quiet port looks quiet, a hot one looks hot, and nothing loops. Values outside the range are clamped.
A port in activePorts with no entry in portLevels renders as a solid fill instead.
The expanding ring is reserved for genuinely discrete moments: the card fires a single ping when a port enters activePorts, so one ping means one thing happened. Under prefers-reduced-motion: reduce the card schedules no ping at all.
<NbBlueprintCard
id="filter"
title="Low-pass"
:ports="ports"
:connected-ports="['in', 'out']"
:active-ports="['in', 'out']"
:port-levels="{ in: inputLevel, out: outputLevel }"
/>Three cards at different levels. The fill is the port's own colour, so a busy graph still reads by node:
portLevels is an ordinary reactive prop, so write it at frame rate, not at audio rate. For audio-rate values, use the blueprint's non-reactive live channel instead, which the PixiJS renderer reads on its own throttled tick.
Multi-channel ports
A port that carries multiple channels (a stereo pair, a multi-output bus, a multi-channel MIDI port) can declare them inline via the channels array instead of writing one entry per channel by hand. The card always renders one pin per channel; there is no "bundle" or expand/collapse, so wires always land on a specific channel and the routing is visually unambiguous.
Each channel pin is addressable in connectedPorts and in IBlueprintConnection records as ${port.id}/${channel.id}.
<template>
<NbBlueprintCard
id="reverb"
title="Hall Reverb"
category="effect"
color="#10b981"
:ports="[
{
id: 'in',
label: 'Stereo In',
type: 'input',
dataType: 'audio:stereo',
channels: [
{ id: 'l', label: 'L' },
{ id: 'r', label: 'R' },
],
},
{
id: 'out',
label: 'Stereo Out',
type: 'output',
dataType: 'audio:stereo',
channels: [
{ id: 'l', label: 'L' },
{ id: 'r', label: 'R' },
],
},
]"
:connected-ports="['in/l', 'in/r', 'out/l']"
/>
</template>Inline port labels
Ports default to tooltip-only labels (hover the pin to see them). For nodes where the port name is the primary information, set the per-port showLabel: true, or apply a card-level default with showPortLabels.
showPortLabels accepts 'left', 'right', 'both', or false. Per-port showLabel always overrides the card-level default. When the inline label is shown, the card's body padding bumps automatically so the title and parameters do not overlap the labels.
For multi-channel ports, the label rendered next to each pin is the channel's label (e.g. L, R); the parent port's label appears in the tooltip (e.g. Stereo In . L).
<!-- Audio interface with eight named outputs -->
<NbBlueprintCard
id="iface"
title="Focusrite Scarlett"
category="i/o . hardware"
color="#f97316"
show-port-labels="right"
:ports="[
{
id: 'inputs',
label: 'Inputs',
type: 'output',
dataType: 'audio:bus',
channels: Array.from({ length: 8 }, (_, i) => ({
id: `i${i + 1}`,
label: `I${i + 1}`,
})),
},
]"
/>
<!-- Single labeled MIDI port via per-port showLabel -->
<NbBlueprintCard
id="midiport"
title="Roland A-49"
category="i/o . midi"
color="#a855f7"
:ports="[
{
id: 'midi',
label: 'MIDI Out',
type: 'output',
dataType: 'midi',
showLabel: true,
},
]"
/>Selected state
Pass selected to draw the card's border in its own identity colour, with a matching inset line. Selection is a colour change and nothing else: it does not lift the card or cast a shadow, because moving a card moves its pins, and its pins are where its wires are anchored.
Enable toggle and disabled state
When enabled is passed, a compact accent-tinted toggle renders in the header. Disabled cards drop to 55% opacity, collapse the body, and append " . off" to the category tag.
Parameter rows
Use the parameters prop to display structured data inside the card body. Each row has a monospaced label, a value, an optional unit, and an optional progress bar.
<template>
<NbBlueprintCard
id="terrain"
title="Terrain"
category="geometry"
color="#6366f1"
:parameters="[
{ label: 'elevation', value: 6, bar: 60 },
{ label: 'seed', value: '0x2A' },
]"
/>
</template>Status
Three states, each a distinct glyph so they stay apart in greyscale, for a reader with a colour vision deficiency, and at the zoom levels where a coloured dot would be sub-pixel.
The glyph is a header cell rather than part of the title, so a long title ellipsises without pushing the status out of view.
Removable
Custom body content
Anything placed in the default slot renders inside the card body, below the parameters.
Density
density controls how tightly the card packs its chrome. Set it once on NbBlueprint and every card inherits it; a card can still override its own.
| Density | Header | Category line | Parameter rows | First pin |
|---|---|---|---|---|
'default' | 34px, or 51px when the card has a category | Shown | 24px | 17px / 26px |
'compact' | 26px | Hidden | 20px | 13px |
'compact' is worth reaching for once a graph is past roughly twenty nodes, where the headers are more of the canvas than the graph is.
Density changes chrome only. Port width, height, pitch and hit area are identical at both densities, so switching density does not move a single wire. The one thing that does move is --nb-blueprint-port-top, which tracks the header it is meant to line up with.
The pins sit at the same offset in both, which is the point: switching density across a whole canvas does not move a single wire.
<!-- Every card in this canvas is compact... -->
<NbBlueprint density="compact">
<NbBlueprintCard id="a" title="Gain" />
<!-- ...except this one. -->
<NbBlueprintCard id="b" title="Master bus" density="default" />
</NbBlueprint>Keyboard and assistive technology
The card is a focusable role="group", labelled with its title, category, status and enabled state. Every control inside it is a real button with its own accessible name.
| Key | Where | Does |
|---|---|---|
| Tab | Anywhere | Moves through the card and its controls. |
| ← → ↑ ↓ | Card | Moves the card by one canvas unit. |
| Shift + arrow | Card | Moves it by ten. |
| Enter / Space | Port | Starts a connection, or completes one already started. |
| Esc | Port | Abandons a connection in progress. |
| Enter / Space | Collapse chevron, toggle, remove | Activates that control. |
Connecting by keyboard walks the same two-step path the mouse does and goes through the same handlers, so a keyboard connection is indistinguishable from a dragged one to the host. While a connection is in progress, valid targets ring and invalid ones dim exactly as they do for the mouse, which is what makes the keyboard flow navigable at all.
Nudging is routed through the parent NbBlueprint, so it reports itself with the same move event a drag does, and nudging a card that is part of the selection moves the whole selection, again matching the mouse. Positions have one path out of the component, not two.
Focusing a card also selects it, so the inspector and the canvas agree about what the user is looking at.
Inside a blueprint
For a complete example wiring cards and ports into an NbBlueprint canvas, see the Blueprint docs.