Skip to content

Frame and panel door

projects.frame_and_panel_door

A door of four raised panels in a mortise-and-tenon frame; outside sizes.

Two stiles, a top, a middle and a bottom rail, and between the rails above and below the middle one a short muntin. A groove runs through along the inside edges of all of them, centered in the thickness, as cut on a table saw. The rails are tenoned into the stiles, the muntins into the rails, the tenons as thick as the grooves are wide and with shoulders as wide as they are deep, so that the grooves run out beside them. At the ends of the stiles, the tenons of the top and the bottom rails have haunches filling the grooves; the muntins' short tenons leave room for each other in the middle rail.

The panels, the grain upright, are raised on the front, their backs flat: the bevel, as planed by hand, runs from the field right out to the edges, at the mouths of the grooves (bevel out from the field) as thick as the grooves are wide, and on into them, getting thinner; the field of a 14 mm panel lies 1 mm behind the face of a 22 mm frame. They aren't glued, but float in the grooves: the edges are clearance short of the bottom across the grain, so the panels can swell, and 1 mm along it.

Frame and panel door, assembled

Frame and panel door, exploded

Parameters

Parameter Default
height 800
width 500
member_width 65
groove_width 8
groove_depth 10
tenon_length 40
muntin_tenon_length 25
bevel 30
clearance 2
stock 22 mm board, oak
panel_stock 14 mm board, oak

Change them in a design file (see Change an example):

from woodwright.examples import projects

design = projects.frame_and_panel_door(height=1000)

Drawings

The plans in one PDF: plans.pdf.

Assembly

assembly

Parts

parts

Muntin

muntin

Panel

panel

Rail

rail

Rail middle

rail middle

Stile

stile

Cut list

stile        800 x 65 x 22 mm  oak, 2 pieces: stile_left, stile_right
             - 3 x mortise 45 x 8 mm, 42 deep, in back_edge
             - haunch mortise 10 x 8 mm, 10 deep, in back_edge at right_end
             - haunch mortise 10 x 8 mm, 10 deep, in back_edge at left_end
             - groove 8 mm, 10 deep, in back_edge
rail         450 x 65 x 22 mm  oak, 2 pieces: rail_top, rail_bottom
             - haunched tenon 45 x 8 mm, 40 long, haunch 10 long, on left_end
             - haunched tenon 45 x 8 mm, 40 long, haunch 10 long, on right_end
             - mortise 45 x 8 mm, 27 deep, in back_edge
             - groove 8 mm, 10 deep, in back_edge
rail_middle  450 x 65 x 22 mm  oak
             - tenon 45 x 8 mm, 40 long, on left_end
             - tenon 45 x 8 mm, 40 long, on right_end
             - mortise 45 x 8 mm, 27 deep, in front_edge
             - mortise 45 x 8 mm, 27 deep, in back_edge
             - groove 8 mm, 10 deep, in front_edge
             - groove 8 mm, 10 deep, in back_edge
muntin       352.5 x 65 x 22 mm  oak, 2 pieces: muntin_top, muntin_bottom
             - tenon 45 x 8 mm, 25 long, on right_end
             - tenon 45 x 8 mm, 25 long, on left_end
             - groove 8 mm, 10 deep, in front_edge
             - groove 8 mm, 10 deep, in back_edge
panel        320.5 x 168.5 x 14 mm  oak, 4 pieces: panel_top_left, panel_top_right, panel_bottom_left, panel_bottom_right
             - chamfer 30 x 6 mm on bottom_face_front_edge_arris, running out past front_edge
             - chamfer 30 x 6 mm on bottom_face_back_edge_arris, running out past back_edge
             - chamfer 30 x 6 mm on bottom_face_left_end_arris, running out past left_end
             - chamfer 30 x 6 mm on bottom_face_right_end_arris, running out past right_end

Code

From woodwright/examples/projects.py, which imports the names it uses (see From an example to your own design):

def frame_and_panel_door(
    height: float = 800,
    width: float = 500,
    member_width: float = 65,
    groove_width: float = 8,
    groove_depth: float = 10,
    tenon_length: float = 40,
    muntin_tenon_length: float = 25,
    bevel: float = 30,
    clearance: float = 2,
    stock: Stock = DOOR_OAK,
    panel_stock: Stock = PANEL_OAK,
) -> Assembly:
    """A door of four raised panels in a mortise-and-tenon frame; outside sizes.

    Two stiles, a top, a middle and a bottom rail, and between the rails
    above and below the middle one a short muntin. A groove runs through
    along the inside edges of all of them, centered in the thickness, as
    cut on a table saw. The rails are tenoned into the stiles, the muntins
    into the rails, the tenons as thick as the grooves are wide and with
    shoulders as wide as they are deep, so that the grooves run out beside
    them. At the ends of the stiles, the tenons of the top and the bottom
    rails have haunches filling the grooves; the muntins' short tenons
    leave room for each other in the middle rail.

    The panels, the grain upright, are raised on the front, their backs
    flat: the bevel, as planed by hand, runs from the field right out to the
    edges, at the mouths of the grooves (`bevel` out from the field) as
    thick as the grooves are wide, and on into them, getting thinner; the
    field of a 14 mm panel lies 1 mm behind the face of a 22 mm frame. They
    aren't glued, but float in the grooves: the edges are `clearance` short
    of the bottom across the grain, so the panels can swell, and 1 mm along
    it.
    """
    rail_length = width - 2 * member_width

    def corner(rail_end: SideRef, stile_edge: SideRef, position: Position, outer: SideRef) -> Joint:
        return MortiseTenon(
            tenon=rail_end,
            mortise=stile_edge,
            position=position,
            tenon_length=tenon_length,
            haunch=outer,
            haunch_length=groove_depth,
            tenon_thickness=groove_width,
            shoulder=groove_depth,
        )

    asm, (stile_left, stile_right, rail_top, rail_bottom) = _frame(
        "frame_and_panel_door", corner, height, rail_length, member_width, stock
    )
    # The rails' front edges are at the top.
    rail_middle = asm.add(Board("rail_middle", stock, length=rail_length, width=member_width))
    middle = (height - member_width) / 2
    for rail_end, stile_edge, stile_end in [
        (rail_middle.left_end, stile_left.back_edge, stile_left.left_end),
        (rail_middle.right_end, stile_right.front_edge, stile_right.left_end),
    ]:
        asm.join(
            MortiseTenon(
                tenon=rail_end,
                mortise=stile_edge,
                position=offset(middle, stile_end),
                tenon_length=tenon_length,
                tenon_thickness=groove_width,
                shoulder=groove_depth,
            )
        )
    # Standing like the stiles: their right ends up, back edges to the right.
    opening_height = middle - member_width
    muntins = []
    for name, top, bottom in [
        ("top", rail_top, rail_middle),
        ("bottom", rail_middle, rail_bottom),
    ]:
        muntin = asm.add(Board(f"muntin_{name}", stock, length=opening_height, width=member_width))
        for end, rail_edge in [
            (muntin.right_end, top.back_edge),
            (muntin.left_end, bottom.front_edge),
        ]:
            asm.join(
                MortiseTenon(
                    tenon=end,
                    mortise=rail_edge,
                    tenon_length=muntin_tenon_length,
                    tenon_thickness=groove_width,
                    shoulder=groove_depth,
                )
            )
        muntins.append(muntin)

    def into_groove(edge: SideRef, groove: SideRef, position: Position, length: float) -> None:
        # The bevelled edge goes in whole, the back of the panel against the
        # back of the groove.
        asm.join(
            TongueAndGroove(
                tongue=edge,
                groove=groove,
                tongue_thickness=groove_width,
                tongue_length=length,
                tongue_offset=0,
                cut_tongue=False,
                position=position,
                across=offset((stock.thickness - groove_width) / 2),
                clearance=groove_depth - length,
            )
        )

    # Standing like the stiles too, their reference faces at the back: the
    # front edges to the left, the right ends up.
    opening_width = (rail_length - member_width) / 2
    edge_reach, end_reach = groove_depth - clearance, groove_depth - 1
    for row, muntin, above, under, stile_end in [
        ("top", muntins[0], rail_top.back_edge, rail_middle.front_edge, "right_end"),
        ("bottom", muntins[1], rail_middle.back_edge, rail_bottom.front_edge, "left_end"),
    ]:
        for column, stile_groove, muntin_groove, rail_end in [
            ("left", stile_left.back_edge, muntin.front_edge, "left_end"),
            ("right", stile_right.front_edge, muntin.back_edge, "right_end"),
        ]:
            panel = asm.add(
                Board(f"panel_{row}_{column}", panel_stock, opening_height, opening_width)
            )
            outer, inner = panel.front_edge, panel.back_edge
            if column == "right":
                outer, inner = inner, outer
            stile_position = offset(member_width, getattr(stile_groove.part, stile_end))
            into_groove(outer, stile_groove, stile_position, edge_reach)
            into_groove(inner, muntin_groove, CENTERED, edge_reach)
            for end, groove in [(panel.right_end, above), (panel.left_end, under)]:
                into_groove(end, groove, flush_with(getattr(groove.part, rail_end)), end_reach)
            # On the design box, the bevel ends at the mouth of the groove.
            for side in (panel.front_edge, panel.back_edge, panel.left_end, panel.right_end):
                panel.cut(
                    Chamfer(
                        panel.arris(panel.bottom_face, side),
                        size=bevel,
                        size_b=panel_stock.thickness - groove_width,
                        run_out=True,
                    )
                )
    return asm

Exported with woodwright 0.9.0 on 2026-10-25.