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_lengthlong (the stock line stays shoulder to shoulder; the stock list buys the blank longer).tenon_thicknessruns across the shoulder's narrower side, centred on the mark. Along the wider side the tenon istenon_widthwide, or without it leaves equal shoulders all round. The female mark names the face the tenon enters; the mortise is the tenon grown byclearance / 2per face. - Thickness axis. Always the smaller of the shoulder's two extents (exactly square: the
first axis), never chosen by
tenon_widthorhaunch. Within 2 % of square but not exactly square, with atenon_width/haunchthe compiler warnstenon-axisand 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 amaterialerror naming the outline. clearance 0is 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 #trueasserts it and, withtenon_lengthomitted, computes the length to reach the far face exactly, so a resize cannot silently turn it blind. proudmoves the flush-trim plane out (needs a through tenon).end_chamferneedsproudgreater than zero, less thanproudand less than a quarter oftenon_thickness.haunch length= depth=: thelengthof the tenon's width toward the nearer end of the mortised part stands onlydepthoff 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 displaceddrawtoward the shoulder.shaperound(default) orsquare;sizeis the diameter (or square side, required);count/spacingspread pins across the tenon width.atis the pin's distance from the shoulder (default: half the female's thickness for a through tenon, else half the tenon's length).tapermakes a square pin tapered, andenter(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>: nopart,markorjoinnames it. Needs both parts square to one another (multiple of 90 degrees).wedge(wari-kusabi): needsthrough #true.countsaw kerfskerfwide anddepthdeep from the tenon's end, a flared mouth on the female's exit face (flareat most a quarter offlare_depth). Generated parts<male>@<mark>/Wedge_<k>. Apinand awedgeon one joint must not cross (pin-wedge-clash).key(tusk tenon): needsthrough #truewithproudlarge enough (key-relish). A mortisesizebythicknesscut through the tenon's width;size,thicknessandtaperrequired,drawdefaults to1.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 nominaldiametereven whentaper_to(its tip diameter, less thandiameter) 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_widthoff 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 seeINDEX.mdfor mitred tips). pin,wedgeandkeyeach need the square, axis-aligned frame (pin-frame,wedge-frame,key-frame: unsupported otherwise).wedge-grain,wedge-flare,wedge-kerf,key-relishandkey-mortisename 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_tenonrefuses ataper_tonot less thandiameterand 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" underround_tenon) predates the oblique build. Trust this file andcrates/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"