New Project

Laps, Bridles & Dovetails#

Half lap, cross lap, bridle, box joint, through and half-blind dovetail, and sliding dovetail

Names: half_lap cross_lap bridle box_joint dovetail_through dovetail_half_blind sliding_dovetail depth_ratio cheek_thickness depth slope pin_count finger_width socket_depth housing_depth tongue_thickness stopped front_gap index pin avoid on

When to use#

  • Members that cross (frames, lattices, a trestle's X): half_lap where at least one member ends at the crossing, cross_lap where both run on through it, bridle where the end of one member is forked over the other (an open mortise and tenon).
  • A box or drawer corner (two boards meeting end to face): box_joint for fingers, dovetail_through or dovetail_half_blind for a drawer front.
  • A shelf or divider captured in a housing (a T, not a corner): sliding_dovetail.

The marks, join and the layout rule are in joinery-contract. A joint with a tenon is in joinery-tenons.

Syntax#

joinery half_lap  name=<N> { [depth_ratio <0..1>]  clearance <mm> }
joinery cross_lap name=<N> { [depth_ratio <0..1>]  clearance <mm> }
joinery bridle    name=<N> { cheek_thickness <mm>  depth <mm>  clearance <mm> }
joinery box_joint name=<N> { pin_count <n> | finger_width <mm>   clearance <mm> }
joinery dovetail_through    name=<N> { slope "1:N"  pin_count <n>   clearance <mm> }
joinery dovetail_half_blind name=<N> { slope "1:N"  pin_count <n>  socket_depth <mm>  clearance <mm> }
joinery sliding_dovetail name=<N> {
    slope "1:N"  housing_depth <mm>  tongue_thickness <mm>  clearance <mm>
    [stopped #true  front_gap <mm>]
}

Crossing members (half_lap, cross_lap, bridle). The two boards are laid out through one another at full size; the shared box is the joint. The marks sit on the cheek, inside the stock, each facing the board that lies over it.

  • half_lap/cross_lap: each part loses what of the shared box lies on its mark's side, down to the cheek. depth_ratio (default 0.5) is the share of the male's thickness it loses and must agree with where its mark puts the cheek: a disagreement is an error, not a guess. clearance widens the female's notch but never deepens it, so the cheeks stay flush. A half_lap reaches the end of at least one member; two boards crossing mid-length are a cross_lap.
  • bridle: the female is the fork, open at its end; the male keeps its middle as the tongue. cheek_thickness is each fork cheek, so the slot is thickness - 2 * cheek_thickness wide. depth is how far the slot runs in from the female's end and must reach across the male. Both members are the same thickness, face for face, and both marks sit on the mid-plane (half the thickness down).

Corners (box_joint, dovetail_*). The male (finger or tail board) is written shoulder to shoulder, like a tenoned rail: its end stops at the female's inner face and its mark sits on that end, facing the female's mark on the broad face it bears on. The compiler adds the fingers or tails to the male's end and cuts the sockets from the female; the stock list adds the tails to the male's blank.

  • Write the female full length, running to the male's outer face (a dovetail must end there: the tails slide in along the tail board's thickness).
  • box_joint: pin_count is the male's fingers (2 * pin_count - 1 equal positions, both outer ones the male's), or finger_width, the narrowest finger you want, and the count follows the board (an odd number). One of the two, not both.
  • dovetail_*: slope "1:N" is an enum written as a word (1:8 hardwood, 1:6 softwood; outside 1:4 to 1:12 warns). pin_count counts pins with the half-pin at each edge, so there is one tail fewer. In place of pin_count, a graduated schedule: one pin index=<i> width=<mm> row per tail inside a repeat. socket_depth (half-blind) is how deep the sockets run into the front; the rest is the lap. A through dovetail's tails are as long as the pin board is thick. avoid start=<mm> width=<mm> margin=<mm> (RFC-0070) keeps every tail root clear of a feature cut elsewhere on the same, male board — start=/width= are offsets from the joint's own low end, the same zero a pin index= row's position already is, not the avoided feature's own offset=/start= frame (reuse a dado's run-axis start= to land in the same axis). on=female (a zone on the pin board instead) is refused, not built.

sliding_dovetail is a T: male mark on the shelf's end, female mark on the broad face it meets, square. The housing runs the male's whole depth, walls leaning outward as they go deeper; the male is shouldered down to a matching tapered tongue. housing_depth is how deep it goes and how far the tongue stands, tongue_thickness is the tongue's root width centred on the male mark. stopped #true with front_gap leaves that much uncut at the end nearer the project's front (front=, default neg_y) so the housing does not show.

Example#

A half lap, a cross lap and a bridle. The half lap's marks are on the cheek at mid-thickness with opposite normals; the bridle's female member ends where the male crosses it.

project name="Laps" units=mm schema_version=1 {
    params { glue_clearance 0.2 }
    materials { material Oak type=hardwood }
    // Half lap: each board keeps one half of its thickness at the overlap. The mark of
    // each sits on the cheek (mid-thickness) and faces the other board.
    component LapA role=rail {
        stock Oak 400.0 40.0 20.0
        mark Cheek x=380.0 y=20.0 z=-10.0 normal=neg_z
    }
    component LapB role=rail {
        stock Oak 400.0 40.0 20.0
        mark Cheek x=20.0 y=20.0 z=-10.0 normal=pos_z
    }
    // Cross lap and bridle: members of one thickness, marks on the mid-plane.
    component Bar role=bar {
        stock Oak 300.0 60.0 30.0
        mark Mid x=30.0 y=30.0 z=-15.0 normal=pos_z
        mark MidDown x=30.0 y=30.0 z=-15.0 normal=neg_z
    }
    component Slat role=slat {
        stock Oak 300.0 40.0 20.0
        mark Top x=150.0 y=20.0 z=-10.0 normal=pos_z
        mark Bottom x=150.0 y=20.0 z=-10.0 normal=neg_z
    }
    joinery half_lap name=Lap {
        depth_ratio 0.5
        clearance glue_clearance
    }
    joinery cross_lap name=Cross {
        depth_ratio 0.5
        clearance glue_clearance
    }
    joinery bridle name=Fork {
        cheek_thickness 10.0
        depth 60.0
        clearance glue_clearance
    }
    assembly Frame {
        part Lap_A instance=LapA { by top_sw; pos x=0.0 y=0.0 z=0.0 }
        part Lap_B instance=LapB { by top_sw; pos x=360.0 y=0.0 z=0.0 }
        join joint=Lap male=Lap_A@Cheek female=Lap_B@Cheek

        part Cross_A instance=Slat { by top_sw; pos x=0.0 y=500.0 z=0.0 }
        part Cross_B instance=Slat { by center; pos x=150.0 y=520.0 z=-10.0; rot z=90.0 }
        join joint=Cross male=Cross_A@Top female=Cross_B@Bottom

        part Bridle_A instance=Bar { by top_sw; pos x=0.0 y=1000.0 z=0.0 }
        part Bridle_B instance=Bar { by top_sw; pos x=60.0 y=1000.0 z=0.0; rot z=90.0 }
        join joint=Fork male=Bridle_A@Mid female=Bridle_B@MidDown
    }
}

Corners and the housing: the male's mark is on its end, the female's on a broad face.

project name="Corners" units=mm schema_version=1 {
    params { glue_clearance 0.2 }
    materials { material Oak type=hardwood }
    component Side role=side {
        stock Oak 20.0 300.0 100.0
        mark Inner x=20.0 y=7.5 z=-50.0 normal=pos_x
    }
    component Front role=front {
        stock Oak 200.0 15.0 100.0
        mark End x=0.0 y=7.5 z=-50.0 normal=neg_x
    }
    joinery box_joint name=Box {
        finger_width 20.0
        clearance glue_clearance
    }
    joinery dovetail_through name=Through {
        slope "1:8"
        pin_count 3.0
        clearance glue_clearance
    }
    joinery dovetail_half_blind name=HalfBlind {
        slope "1:8"
        pin_count 3.0
        socket_depth 12.0
        clearance glue_clearance
    }
    joinery sliding_dovetail name=Housing {
        slope "1:6"
        housing_depth 7.0
        tongue_thickness 10.0
        clearance glue_clearance
        stopped #true
        front_gap 12.0
    }
    assembly Boxes {
        part S1 instance=Side { by top_sw; pos x=0.0 y=0.0 z=0.0 }
        part F1 instance=Front { by top_sw; pos x=20.0 y=0.0 z=0.0 }
        part S2 instance=Side { by top_sw; pos x=0.0 y=400.0 z=0.0 }
        part F2 instance=Front { by top_sw; pos x=20.0 y=400.0 z=0.0 }
        part S3 instance=Side { by top_sw; pos x=0.0 y=800.0 z=0.0 }
        part F3 instance=Front { by top_sw; pos x=20.0 y=800.0 z=0.0 }
        part S4 instance=Side { by top_sw; pos x=0.0 y=1200.0 z=0.0 }
        part F4 instance=Front { by top_sw; pos x=20.0 y=1200.0 z=0.0 }
        join joint=Box male=F1@End female=S1@Inner
        join joint=Through male=F2@End female=S2@Inner
        join joint=HalfBlind male=F3@End female=S3@Inner
        join joint=Housing male=F4@End female=S4@Inner
    }
}

Pitfalls and diagnostics#

  • A lap whose depth_ratio disagrees with its mark is refused; so is a mark off the shared cheek plane (contact), marks that do not face each other (facing), and members that cross skewed rather than square to one another (faces-overlap: skewed laps are unsupported, RFC-0022).
  • A half_lap crossing mid-length of both members is refused with "this is a cross_lap"; a bridle whose female does not end where the male crosses it is refused with "a member crossing in its middle is a cross_lap" (kind).
  • A bridle needs members the same thickness, face for face (material), marks half the thickness down (contact), cheek_thickness leaving a slot (params), and depth reaching across the male and not running the female's whole length (remaining-wall).
  • Corners. Refused: the male mark on a broad face, the female mark on an end or edge, the male's end not wholly on the face, fewer than three fingers, a finger, tail root or pin under 3 mm, a half-blind socket as deep as the pin board is thick, a box joint with both or neither of pin_count and finger_width, dovetail sockets closed on both sides. Warned: a lap thinner than max(3 mm, 15 %), a fractional pin_count, a slope outside 1:4 to 1:12.
  • slope "1:8" is a word, never a parameter's name. Do not give a dovetail_* both pin_count and a pin schedule.
  • A lipped (overlay) drawer front that runs past the side: rabbet its back face back to the side at least as deep as the sockets (bedside_cabinet.kdl).
  • A groove through the male's end stops at the shoulder; run it on into the tails with stopped=#true where something (a drawer floor) sits in it (blanket_chest.kdl).
  • Choose the joint a woodworker would use: the validator cannot tell a poor choice (box joints on a corner that needs a mitre) from a good one.

See also#

  • docs/KDL_AUTHORING.md §8 ("Tenons, laps, bridles and mitres", "Box joints and dovetails", "Sliding dovetail housing"); ADR-0013 (and its lap addendum), ADR-0015 (graduated schedules); RFC-0124 (sliding dovetail).
  • examples/desk.kdl (box_joint), examples/dining_chair.kdl (half_lap), examples/panel_door_and_window.kdl (cross_lap), examples/bedside_cabinet.kdl, examples/blanket_chest.kdl, examples/biedermeier_box.kdl, examples/welsh_dresser.kdl (dovetails), examples/bookcase.kdl (sliding_dovetail).
  • joinery-contract.md, joinery-tenons.md, joinery-frames.md, edge-and-face-cuts.md.

Names#

Every name this page declares, how the example library uses it, and the shortest real use.

half_lap#

joinery kind; value in 4 of 116 examples (4 uses).

Shortest real use, from pierced_splat_side_chair.kdl:

joinery half_lap name=SlipSeatCornerJoint {
    depth_ratio 0.5
    clearance glue_clearance
}

cross_lap#

joinery kind; value in 3 of 116 examples (3 uses).

Shortest real use, from mid_century_slat_bench.kdl:

joinery cross_lap name=SlatLap {
    depth_ratio 0.25
    clearance glue_clearance
}

bridle#

joinery kind; not used by any example yet.

box_joint#

joinery kind; value in 6 of 116 examples (7 uses).

Shortest real use, from danish_teak_sideboard.kdl:

joinery box_joint name=CaseCorner {
    finger_width finger_w
    clearance glue_clearance
}

dovetail_through#

joinery kind; value in 10 of 116 examples (10 uses).

Shortest real use, from arts_crafts_cabinet_on_stand.kdl:

joinery dovetail_through name=CarcaseDovetail {
    slope "1:8"
    pin_count 5
    clearance glue_clearance
}

dovetail_half_blind#

joinery kind; value in 17 of 116 examples (17 uses).

Shortest real use, from welsh_dresser.kdl:

joinery dovetail_half_blind name=DrawerDovetail {
    slope "1:6"
    pin_count 3.0
    socket_depth 10.0
    clearance glue_clearance
}

sliding_dovetail#

joinery kind; value in 8 of 116 examples (8 uses).

Shortest real use, from studio_cabinet_on_stand.kdl:

joinery sliding_dovetail name=ShelfDovetail {
    slope "1:8"
    housing_depth "case_th * 0.4"
    tongue_thickness "shelf_th * 0.6"
    clearance glue_clear
}

depth_ratio#

node in 7 of 116 examples (7 uses).

Shortest real use, from dining_chair.kdl:

depth_ratio 0.5

cheek_thickness#

not used by any example yet.

depth#

property; node in 59 of 116 examples (122 uses); property in 95 of 116 examples (571 uses); value in 33 of 116 examples (74 uses).

Shortest real use, from glazed_vitrine_whiplash_crest.kdl:

depth 8.0

slope#

word-valued property; node in 23 of 116 examples (35 uses).

Shortest real use, from art_deco_cabinet_on_fuseau_legs.kdl:

slope "1:8"

pin_count#

node in 21 of 116 examples (28 uses).

Shortest real use, from arts_crafts_cabinet_on_stand.kdl:

pin_count 5

finger_width#

node in 4 of 116 examples (5 uses).

Shortest real use, from danish_teak_sideboard.kdl:

finger_width finger_w

socket_depth#

node in 17 of 116 examples (18 uses); value in 1 of 116 examples (1 uses).

Shortest real use, from welsh_dresser.kdl:

socket_depth 10.0

housing_depth#

node in 11 of 116 examples (15 uses); value in 7 of 116 examples (16 uses).

Shortest real use, from pennsylvania_schrank.kdl:

housing_depth    8.0

tongue_thickness#

node in 12 of 116 examples (18 uses); value in 3 of 116 examples (3 uses).

Shortest real use, from dining_table.kdl:

tongue_thickness 10.0

stopped#

property; node in 7 of 116 examples (7 uses); property in 40 of 116 examples (135 uses).

Shortest real use, from biedermeier_chest_of_drawers.kdl:

stopped #true

front_gap#

node in 7 of 116 examples (8 uses); value in 1 of 116 examples (1 uses).

Shortest real use, from danish_teak_sideboard.kdl:

front_gap 10.0

index#

property; property in 1 of 116 examples (1 uses).

Shortest real use, from blanket_chest.kdl:

pin index=i width="i == 2.0 ? 18.0 : (carcass_height - carcass_th) / 8.0"

pin#

node in 19 of 116 examples (35 uses).

Shortest real use, from appalachian_ladder_back_side_chair.kdl:

pin shape=round size=3.0

avoid#

node in 1 of 116 examples (1 uses).

Shortest real use, from blanket_chest.kdl:

avoid start="carcass_height - till_lid_th - till_height" width=till_stock_th

on#

word-valued property; not used by any example yet.

Settings

Region

A region picks sensible defaults; change any of these separately.

Units

In inches, type lengths the way you say them: 34 1/2, 3/4", 1'-2" or 12.7mm. A length that can't be read is flagged, never rounded to something else.

Paper
Date order
Clock