---
title: "Parts"
description: "Blanks, grain direction and named instances — the board a part is cut from, before anything is cut into it."
url: https://kaicad.barpom.xyz/docs/schema/parts
schema: "0.1"
source: packages/spec/src/design.ts
---

# Parts

A part is a **blank** — a rectangular board — plus the features cut into it. Nothing about
where it sits in the piece appears here; that is what [joints](/docs/schema/joints) are for.

```yaml
parts:
  - id: side_rail
    label: Side rail
    instances: [left, right]
    material: rail_stock
    grain: length
    blank:
      length: deck_length
      width: rail_stock.width
      thickness: rail_stock.thickness
    features:
      - kind: roundover
        edges: [top_left, top_right]
        radius: 3
```

| Key | | |
| --- | --- | --- |
| `id` | required | Referenced by joints, expressions and groups. |
| `material` | required | A `materials` id. |
| `blank` | required | `length`, `width`, `thickness` — all expressions. |
| `label` | | What the cut list and the drawing call it. |
| `instances` | | Named copies — see below. |
| `grain` | | `length` (default), `width` or `thickness`. |
| `features` | | What is cut into it — see [Features](/docs/schema/features). |
| `note` | | Free text, reaches the cut list and the drawing. |

## The blank is the board, not the finished part

Give the size **before** the joinery. A tenon does not lengthen the blank and a dado does not
shorten it; the resolver subtracts the features itself. Blank dimensions are what the cut list
asks the timber yard for, with the material's rough allowance added on top.

Size them from parameters rather than typing measurements:

```yaml
blank:
  length: deck_length                       # a derived parameter
  width: rail_stock.width                   # the full width of the stock
  thickness: rail_stock.thickness
```

```yaml
blank:
  length: side_rail.blank.length - 150       # another part's blank
```

The second form is how parts stay in step. The cleat is always 150 mm shorter than the rail,
whatever the rail becomes.

## `length` is the long axis, and it decides everything else

The three names are not interchangeable labels — they are the part's own coordinate axes, and
every face, edge and feature is named against them:

```
+X = length      start ←→ end
+Y = width       left  ←→ right
+Z = thickness   bottom ←→ top
```

A board 800 × 195 × 25 is `length: 800, width: 195, thickness: 25`. Writing it as
`length: 25` is legal and will produce a part whose `top` face is 800 × 195 — which is not
what any of your joints expect.

The full coordinate model is [Anchors](/docs/schema/anchors).

## `grain`

Which blank axis the long grain runs along. Defaults to `length`.

```yaml
grain: length     # the usual case
grain: width      # a panel whose grain runs across its length
```

It drives the wood-movement checks, the end-grain fastener checks and the nesting on the cut
list. A panel captured in a groove on all four sides will move across its grain and split the
frame if it was not allowed room, and the validator can only tell you which way it will move
if you have said which way the grain runs.

```yaml
- id: headboard_panel
  material: panel_stock
  grain: width
  blank: { length: 1600, width: 560, thickness: 18 }
```

That panel is wider than it is tall in the piece, and its grain runs vertically — the `width`
axis. Say so.

## `instances`

Named copies of one part, cut from one cut-list row.

```yaml
- id: head_post
  instances: [left, right]
```

You then have `head_post@left` and `head_post@right`, and anchors address them with the `@`:

```yaml
mate:
  a: side_rail@left:face.start
  b: head_post@left:face.top
```

**`left` and `right` are special.** A part with exactly those two instances is **mirrored**
about the design's YZ plane. Any other set of instance names is copied unmirrored, and each
copy is positioned by its own joints.

### Mirrored instances are the sharpest edge in the model

Mirroring flips the part, and it takes the part's faces and features with it. The consequences
are worth reading twice:

- **The inner face of the left post is its `right` face; the inner face of the right post is
  its `left` face.** Any joint that talks about "the inside" has to name a different face for
  each side, which is why rail-to-post joints are usually written out twice rather than with
  `for_each`.
- **A feature arrives upside down on the mirrored copy.** A groove on the `right` face of the
  left post is on the `right` face of the right post too — rotated 180°. A panel mating into
  both will go in backwards on one side unless you correct it with `align.roll: 180`.

Both of these are visible in the [worked example](/app/d/queen-platform-bed): the headboard
panel seats into the left post's groove normally and into the right post's with `roll: 180`.

If a part has three or more copies that are genuinely identical and evenly spaced — slats,
shelves, pickets — do not use `instances`. Use an [array](/docs/schema/arrays).

## `note`

```yaml
- id: centre_leg
  note: Trim to fit on site — floors are rarely flat.
```

Free text that travels to the cut list and the drawing. Use it for the thing a person needs to
know that no number captures.
