---
title: "Features"
description: "The ten things that can be cut into a blank — dados, grooves, rabbets, mortises, holes, pockets, notches, chamfers, roundovers and tapers — and how each one is positioned."
url: https://kaicad.barpom.xyz/docs/schema/features
schema: "0.1"
source: packages/spec/src/design.ts
---

# Features

Features are cut into a part's blank. They do not change the blank's size: a dado does not
shorten a board and a tenon does not lengthen one. The blank is what you buy; the features are
what you do to it.

```yaml
parts:
  - id: head_post
    blank: { length: 900, width: 75, thickness: 75 }
    features:
      - kind: groove
        id: panel_slot
        on: right
        along: length
        width: 18
        depth: 10
        center: true
        stopped: 40
      - kind: chamfer
        edges: [end_*]
        size: 3
```

Every feature takes an optional `id`. **Give it one if a joint needs to mate to it** — that is
what turns the feature into an [anchor](/docs/schema/anchors#feature-anchors-are-the-ones-that-matter),
and it is how a panel and the groove it sits in stay in step.

Most features also take an `on:` face. Faces, edges and the (u, v) frames they are measured in
are all on the [Anchors](/docs/schema/anchors) page.

## Positioning along an axis

Dados, grooves, mortises and notches sit somewhere along an axis, and there are four ways to
say where. **Give exactly one.**

| Form | Means |
| --- | --- |
| `from: 120` | 120 mm from the `start` end to the feature's **near edge** |
| `to: 300` | 300 mm from the `start` end to the feature's **far edge** |
| `at: 210` | 210 mm from the `start` end to the feature's **centre** |
| `center: true` | Centred on the axis |

Giving two, or none, is a [`RES-`](/docs/validation/error-codes) finding rather than a guess.
The resolver will not pick for you, because picking wrong puts a trench in the wrong place and
nothing downstream would notice.

`from` and `to` may be expressions, so a feature can follow a parameter:

```yaml
- kind: dado
  on: right
  from: deck_height - shelf_stock.thickness
  width: 18
  depth: 9
```

## The trenches

### `dado`

A square-bottomed trench **across** the grain, running the full width of the face unless
stopped.

```yaml
- kind: dado
  id: shelf_seat
  on: right
  along: width        # optional — defaults to the short axis of `on`
  at: 400
  width: 18           # the width of the trench, i.e. the thickness of what sits in it
  depth: 9
  stopped: 12         # optional: stop 12 mm short of the far edge
```

`width` is the trench's width, not the part's. Size it to the **thickness** of the part that
seats in it — or better, reference that part directly:

```yaml
width: shelf.blank.thickness
```

### `groove`

The same cut, **with** the grain. It is a separate kind because the validation differs: a
groove along the grain and a dado across it fail in different ways, and a groove is what
captures a panel that will move seasonally.

Same keys as `dado`. `stopped:` leaves a blind end, which is what keeps a groove from showing
on the front edge of a post.

### `rabbet`

An L-shaped cut along one edge of a face.

```yaml
- kind: rabbet
  id: back_seat
  on: right             # one of the six faces
  edge: end             # which edge of that face it runs along
  width: 12
  depth: 9
  from: 0               # optional — run only part of the edge
  to: 400
```

Note `edge:` here names an edge of the face, not one of the twelve blank edges.

### `mortise`

A blind or through rectangular slot.

```yaml
- kind: mortise
  id: tenon_seat
  on: right
  along: length         # the slot's long axis
  at: 300
  length: 60            # along `along`
  width: 12             # across it
  depth: 30             # omit and set `through: true` for a through mortise
  ends: round           # `round` (routed) or `square` (chiselled)
```

`ends` matters to whoever cuts it: a router leaves round ends and a chisel leaves square ones,
and a tenon has to match whichever you chose.

### `notch`

A rectangle removed from a corner or an edge — the classic leg-and-apron cut-out.

```yaml
- kind: notch
  on: top
  edge: start
  from: 0
  length: 40
  depth: 20
```

## Holes and pockets

### `hole`

```yaml
- kind: hole
  on: top
  diameter: 8
  depth: 12             # or: through: true
  at: { u: 100, v: center }
  counterbore: { diameter: 14, depth: 7 }
  countersink: { diameter: 8.4, angle: 90 }
```

`at` is a position in the face's own (u, v) frame, measured from its minimum corner. Either
value may be `center`.

For more than one hole, use `pattern:` instead of `at:` — see below.

> **Most holes should not be authored.** Every clearance hole, pilot hole, counterbore and
> countersink that belongs to a fastener comes from the fixture's machining recipe when you
> declare a [joint](/docs/schema/joints). Hand-placing a screw hole means the validator cannot
> check its edge distance or thread engagement, because as far as it knows there is no screw
> there. Author a hole for a cable exit, a bolt you are supplying yourself, or a shelf-pin
> array — not for a screw.

### `pocket`

A flat-bottomed rectangular recess that reaches no edge.

```yaml
- kind: pocket
  on: bottom
  length: 120
  width: 40
  depth: 6
  at: { u: center, v: center }
```

## Patterns

A repeated position. This is the shape behind every run of screw holes and every shelf-pin
line.

```yaml
pattern:
  along: length         # required: which axis the run follows
  count: 10
  spacing: auto         # centre-to-centre, or `auto` to divide the span evenly
  margin: 80            # keep this clear at each end
```

| Key | |
| --- | --- |
| `along` | Required. `length`, `width` or `thickness`. |
| `count` | How many. An expression, so it can be computed. |
| `spacing` | Centre-to-centre, or `auto` to divide the available span evenly. |
| `margin` | Clear space to leave at each end of the span. |
| `from` / `to` | Bound the run explicitly instead of using the whole axis. |
| `center` | Centre the run on the axis. |
| `offset` | Position on the face's *other* axis. Defaults to centred. |
| `at` | Shorthand instead of count and spacing: any of `start`, `end`, `centre`, `ends`. |

```yaml
# Ten screws down a cleat, evenly spread, 80 mm clear at each end.
pattern: { along: length, count: 10, spacing: auto, margin: 80 }

# Two bolts, 100 mm apart, positioned at a computed height.
pattern:
  along: length
  count: 2
  spacing: bolt_pitch
  from: rail_centre_height - bolt_pitch / 2
  to:   rail_centre_height + bolt_pitch / 2

# Shelf pins on the 32 mm system.
pattern: { along: length, count: 12, spacing: 32, from: 200 }
```

The same `pattern` shape is used by `joints[].fasteners.pattern`, which is where you will
write it most of the time.

## Edge treatments

### `chamfer` and `roundover`

Both take edge selectors, and wildcards work:

```yaml
- kind: chamfer
  edges: [top_end, bottom_end, left_end, right_end]
  size: 3

- kind: chamfer
  edges: [end_*]        # the same four, shorter
  size: 3

- kind: roundover
  edges: [top_left, top_right]
  radius: 3
```

Edge names and the wildcard rules are on the [Anchors](/docs/schema/anchors#the-twelve-edges)
page. A chamfer or roundover has no frame, so it cannot be mated to.

A 2–3 mm break on every edge a hand will touch is worth authoring. It is also the cheapest
thing in the file to change later.

### `taper`

A linear taper of one dimension along another axis — table and chair legs.

```yaml
- kind: taper
  along: length         # the axis the taper runs down
  reduce: width         # the dimension that shrinks
  to: 30                # its size at the far end of `along`
  from: 150             # where the taper begins, from the `start` end
  sides: both           # `both` (default), `near` or `far`
```

`sides: far` tapers only the outward face, which is the usual choice for a leg that must stay
square where it meets an apron.

## The ten kinds, at a glance

| Kind | Positioned by | Mateable |
| --- | --- | --- |
| `dado` | `on` + along-axis form | yes |
| `groove` | `on` + along-axis form | yes |
| `rabbet` | `on` + `edge` + `from`/`to` | yes |
| `mortise` | `on` + along-axis form | yes |
| `hole` | `on` + `at` or `pattern` | yes |
| `pocket` | `on` + `at` | yes |
| `notch` | `on` + `edge` + along-axis form | no |
| `chamfer` | `edges` | no |
| `roundover` | `edges` | no |
| `taper` | `along` + `reduce` | no |
