New Project

Tenon Joinery#

Mortise-and-tenon and round tenon: haunch, through, proud, pin, wedge, key, and oblique

Names: mortise_and_tenon round_tenon tenon_thickness tenon_width tenon_length haunch centering through proud end_chamfer pin wedge key taper_to draw enter ends_proud

When to use#

A rail or rung that enters a leg or post: the strong, traditional frame joint. Use mortise_and_tenon for a rectangular tenon (aprons, rails, stretchers) and round_tenon for a turned member (a chair rung, a spindle) into a bored socket. The marks, join and the common layout rule are in joinery-contract.

Syntax#

joinery mortise_and_tenon name=<N> {
    tenon_thickness <mm>  [tenon_length <mm>]  clearance <mm>
    [tenon_width <mm>]  [haunch length=<mm> depth=<mm>]  [centering axis=<x|y|z>]
    [through #true  [proud <mm>]  [end_chamfer <mm>]]
    [pin shape=round|square size= draw= count= spacing= [taper= at= enter= ends_proud= material= label=]]
    [wedge count= kerf= depth= flare= flare_depth= [material= label=]]
    [key size= thickness= taper= [draw= ends_proud= material= label=]]
}
joinery round_tenon name=<N> { diameter <mm>  length <mm>  [taper_to <mm>]  clearance <mm> }
  • Layout. The male mark sits on the shoulder, the end of the male's stock line. The tenon is added outside it, tenon_length long (the stock line stays shoulder to shoulder; the stock list buys the blank longer). tenon_thickness runs across the shoulder's narrower side, centred on the mark. Along the wider side the tenon is tenon_width wide, or without it leaves equal shoulders all round. The female mark names the face the tenon enters; the mortise is the tenon grown by clearance / 2 per face.
  • Thickness axis. Always the smaller of the shoulder's two extents (exactly square: the first axis), never chosen by tenon_width or haunch. Within 2 % of square but not exactly square, with a tenon_width/haunch the compiler warns tenon-axis and names the side it took; make the stock differ by more to get the other orientation.
  • On a cut_path keep="inside" part the tenon stands on the end face the outline leaves (stepped or short faces included); none left is a material error naming the outline.
  • clearance 0 is fine; round sockets are floored just above tessellation so they still clear.
  • Blind or through. A tenon that reaches the far face is through; what would stand proud is cut flush by default. through #true asserts it and, with tenon_length omitted, computes the length to reach the far face exactly, so a resize cannot silently turn it blind.
  • proud moves the flush-trim plane out (needs a through tenon). end_chamfer needs proud greater than zero, less than proud and less than a quarter of tenon_thickness.
  • haunch length= depth=: the length of the tenon's width toward the nearer end of the mortised part stands only depth off the shoulder, and the mortise steps with it.
  • centering axis= names a male axis the mark must be centred on (checked).
  • pin (drawbore): a peg driven across the tenon's thickness through both parts, the tenon's hole displaced draw toward the shoulder. shape round (default) or square; size is the diameter (or square side, required); count/spacing spread pins across the tenon width. at is the pin's distance from the shoulder (default: half the female's thickness for a through tenon, else half the tenon's length). taper makes a square pin tapered, and enter (a male-local face word on the pin's axis, e.g. pos_z) names the cheek it is driven in from, so the small end leaves from the other. It makes its own generated part <male>@<mark>/Pin_<k>: no part, mark or join names it. Needs both parts square to one another (multiple of 90 degrees).
  • wedge (wari-kusabi): needs through #true. count saw kerfs kerf wide and depth deep from the tenon's end, a flared mouth on the female's exit face (flare at most a quarter of flare_depth). Generated parts <male>@<mark>/Wedge_<k>. A pin and a wedge on one joint must not cross (pin-wedge-clash).
  • key (tusk tenon): needs through #true with proud large enough (key-relish). A mortise size by thickness cut through the tenon's width; size, thickness and taper required, draw defaults to 1.5. Generated part <male>@<mark>/Key_0.
  • Oblique shoulder. The tenon runs along the male's shoulder normal as placed and may meet the female face up to 45 degrees off square (MAX_OBLIQUE): a raked leg, a dished spoke. The mortise leans with the tenon and the shoulder is scribed to the face all round.
  • round_tenon: a bored round socket and a cylindrical tenon. Clearance is on the socket only; the tenon is cut at nominal diameter even when taper_to (its tip diameter, less than diameter) makes it a cone. An oblique round tenon is built: the socket leans up to the same 45 degree limit (RFC-0127, closed 2026-09-28). docs/KDL_AUTHORING.md §8 still says it is "square-shouldered only"; that sentence is stale, and the source and the RFC status line are right.

Example#

project name="Tenons" units=mm schema_version=1 {
    params {
        glue_clearance 0.2
        leg_dim 45.0
    }
    materials { material Oak type=hardwood }
    component Leg role=leg {
        stock Oak leg_dim leg_dim 700.0
        mark Face_A x=leg_dim y="leg_dim / 2.0" z=-100.0 normal=pos_x
        mark Face_B x=leg_dim y="leg_dim / 2.0" z=-300.0 normal=pos_x
        mark Face_C x=leg_dim y="leg_dim / 2.0" z=-500.0 normal=pos_x
    }
    component Rail role=rail {
        // runs along x; its end face at x=0 is the shoulder
        stock Oak 400.0 20.0 80.0
        mark Shoulder x=0.0 y=10.0 z=-40.0 normal=neg_x
    }
    component Rung role=rung {
        stock Oak 300.0 30.0 30.0
        mark Shoulder x=0.0 y=15.0 z=-15.0 normal=neg_x
    }
    joinery mortise_and_tenon name=HaunchedJoint {
        tenon_thickness 8.0
        tenon_length 25.0
        clearance glue_clearance
        haunch length=12.0 depth=10.0
        centering axis=z
    }
    joinery mortise_and_tenon name=DrawboredJoint {
        tenon_thickness 8.0
        through #true
        proud 4.0
        end_chamfer 1.0
        clearance glue_clearance
        pin shape=round size=2.5 draw=1.0 count=1
    }
    joinery round_tenon name=RungJoint {
        diameter 16.0
        length 30.0
        taper_to 12.0
        clearance 0.3
    }
    assembly Frame {
        part Leg_A instance=Leg { by top_sw; pos x=0.0 y=0.0 z=0.0 }
        part Rail_A instance=Rail { by top_sw; pos x=45.0 y=12.5 z=-60.0 }
        part Rail_B instance=Rail { by top_sw; pos x=45.0 y=12.5 z=-260.0 }
        part Rung_A instance=Rung { by top_sw; pos x=45.0 y=7.5 z=-485.0 }
        join joint=HaunchedJoint male=Rail_A@Shoulder female=Leg_A@Face_A
        join joint=DrawboredJoint male=Rail_B@Shoulder female=Leg_A@Face_B
        join joint=RungJoint male=Rung_A@Shoulder female=Leg_A@Face_C
    }
}

Pitfalls and diagnostics#

  • Refused: a tenon as thick as its stock; a tenon or tenon_width off its shoulder; a mortise running off the female face; a blind mortise the clearance breaks through; a haunch as wide or long as the tenon or with no nearer end; a shoulder more than 45 degrees off square or off the face; and two tenons meeting inside one part (tenons-meet: two aprons into one leg at one height: stop each short of the other, or see INDEX.md for mitred tips).
  • pin, wedge and key each need the square, axis-aligned frame (pin-frame, wedge-frame, key-frame: unsupported otherwise). wedge-grain, wedge-flare, wedge-kerf, key-relish and key-mortise name the specific refusal.
  • A pin much thicker than a third of the tenon warns (pin-size): typical is a quarter to a third.
  • The mortise and tenon are modelled square-cornered; a router's corner radius is a fabrication question.
  • round_tenon refuses a taper_to not less than diameter and any socket or tenon that would run past its face's edge. Tenon the round part's flat end, not its curved surface.
  • A mark on a curved turned surface is not reachable; tenon into a square pommel (round-stock-sections).
  • Stale elsewhere: docs/KDL_AUTHORING.md §8 ("Square-shouldered only", "Refused: the shoulder off square by any amount" under round_tenon) predates the oblique build. Trust this file and crates/ww-build/src/joinery/round_tenon.rs.

See also#

  • docs/KDL_AUTHORING.md §8 ("Tenons, laps, bridles and mitres"); RFC-0090 (pin, wedge), RFC-0125 (key), RFC-0127 (round tenon), RFC-0022 (oriented marks); ADR-0013.
  • examples/desk.kdl, examples/dining_table.kdl, examples/trestle_table.kdl, examples/wagon_wheel.kdl, examples/three_legged_stool.kdl.
  • joinery-contract.md, joinery-laps-and-boxes.md, joinery-frames.md, round-stock-sections.md, components-and-stock.md (tilt=/heading= marks).

Names#

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

mortise_and_tenon#

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

Shortest real use, from incised_walnut_washstand.kdl:

joinery mortise_and_tenon name=TopTenon {
    tenon_thickness 12.0
    tenon_length 10.0
    clearance glue_clearance
}

round_tenon#

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

Shortest real use, from appalachian_ladder_back_side_chair.kdl:

joinery round_tenon name=RungJ {
    diameter 15.0
    length 26.0
    clearance 0.4
}

tenon_thickness#

node in 80 of 116 examples (190 uses); value in 4 of 116 examples (8 uses).

Shortest real use, from appalachian_ladder_back_side_chair.kdl:

tenon_thickness 8.0

tenon_width#

node in 54 of 116 examples (120 uses); value in 1 of 116 examples (1 uses).

Shortest real use, from pasadena_library_table.kdl:

tenon_width tw

tenon_length#

node in 69 of 116 examples (152 uses); value in 3 of 116 examples (6 uses).

Shortest real use, from high_back_ladder_chair.kdl:

tenon_length 8.0

haunch#

node in 6 of 116 examples (9 uses).

Shortest real use, from filing_cabinet_vertical.kdl:

haunch length=6.0 depth=5.0

centering#

node in 36 of 116 examples (68 uses).

Shortest real use, from arched_crest_side_chair.kdl:

centering axis=z

through#

node in 20 of 116 examples (38 uses).

Shortest real use, from danish_oak_dining_table.kdl:

through #true

proud#

node in 14 of 116 examples (29 uses); value in 2 of 116 examples (2 uses).

Shortest real use, from pasadena_bedside_chest.kdl:

proud tenon_proud

end_chamfer#

node in 11 of 116 examples (20 uses).

Shortest real use, from pasadena_bedside_chest.kdl:

end_chamfer 1.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

wedge#

node in 6 of 116 examples (8 uses).

Shortest real use, from wedged_post_and_rung_stool.kdl:

wedge kerf=3.0 depth=18.0 flare=2.0 flare_depth=10.0

key#

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

Shortest real use, from mission_keyed_bookcase.kdl:

key size=key_size thickness=key_thickness taper=key_taper draw=key_draw \
    ends_proud=key_ends_proud label="Key"

taper_to#

node in 2 of 116 examples (10 uses).

Shortest real use, from windsor_fan_back_side_chair.kdl:

taper_to 9.0

draw#

property; property in 18 of 116 examples (34 uses).

Shortest real use, from danish_oak_dining_table.kdl:

pin shape=round size=peg_size draw=peg_draw count=1

enter#

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

ends_proud#

property; property in 9 of 116 examples (18 uses).

Shortest real use, from arts_crafts_cabinet_on_stand.kdl:

pin shape=round size=peg_size draw=1.0 count=1 ends_proud=0.0 label="Peg"

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