Generate custom 3D-printable keyboard cases without CAD or programming knowledge.
A parametric keyboard case generator that creates production-ready STL files from simple configuration. Design split keyboards, unibody boards, macropads, or any custom layout — all through TypeScript configuration files.
- No CAD software needed — define your keyboard with parameters, not 3D modeling
- No programming required — just edit configuration values in TypeScript files
- Direct STL output — generate print-ready files without running or even installing OpenSCAD
- Fully parametric — every dimension calculated from your configuration
- Organic outlines — key-following non-rectangular case shapes, not just bounding boxes
- Snap-fit assembly — no screws needed, top and bottom snap together
- Magnetic tenting — built-in MagSafe ring support for phone holder mounts
Built-in support for popular mechanical switches:
- Kailh Choc — low-profile switches (17.7×16.6mm spacing)
- Cherry MX — standard mechanical switches (18.6×18.6mm spacing)
Add custom switch types by defining specifications in src/switches.ts:
mySwitch: {
description: 'My Custom Switch',
outerWidth: 15.0,
outerHeight: 15.0,
innerWidth: 13.8,
innerHeight: 13.8,
wallThickness: 1.3,
depth: 4.35,
ledgeHeight: 2.2,
spacingX: 17.7,
spacingY: 16.6,
}Built-in connector types:
- USB-C — pill-shaped female socket
- TRRS — 3.5mm audio jack (for split keyboards)
- Power Button — rectangular button cutout
Create custom connectors with any shape:
circle— circular cutout (specify radius)pill— rounded rectangle (specify circle radius + center distance)square— rectangular cutout (specify width + height)
Connectors can be placed on any face (top, bottom, left, right) with precise 0–1 positioning along the edge.
Example custom connector:
myConnector: {
description: 'My Custom Port',
geometry: {
type: 'circle',
radius: 4.0
}
}Fully flexible row-based layouts with per-row control:
rowLayout: [
{ start: 0, length: 6, offset: 0 }, // 6 keys starting at column 0
{ start: -1, length: 6, offset: 2 }, // 6 keys starting at column -1, 2mm stagger
{ start: 0, length: 5, offset: 5 }, // 5 keys starting at column 0, 5mm stagger
]start— starting column position (can be negative for left offset)length— number of keys in the rowoffset— column stagger in millimetersthumbAnchor— optional key index to anchor the thumb cluster to this row
Optional thumb clusters with independent control:
- Number of keys
- Spacing between keys
- Rotation angle
- Offset position
- Per-key rotation and offsets
Split keyboard support with automatic mirroring for left/right halves.
Snap-fit rounded-corner enclosure with no screws required. Full control over case dimensions:
- Plate thickness (top, bottom, walls)
- Edge margins (uniform or per-side)
- Electronics cavity depth
- Rubber feet / magnet socket positions and sizes
Two enclosure outline modes:
rect (default) — traditional rectangular bounding box with rounded corners:
enclosure: {
cornerRadius: 3, // corner rounding radius (mm)
}organic — key-following outline that hugs the layout, creating non-rectangular case shapes:
enclosure: {
outline: {
type: 'organic',
keyPadding: 1.3, // mm from key edge to inner wall
closingRadius: 10, // concavity control (higher = smoother concave regions)
cornerRadius: 3, // outline corner rounding
resolution: 1.0, // grid cell size in mm (smaller = more detailed)
simplifyEpsilon: 0.8, // point simplification tolerance in mm
},
}git clone [email protected]:20lives/flatboard.git
cd flatboard
bun installbun run listOutput:
Available keyboard profiles:
• corne: 23 keys {0:2,0:3,0:3,0:3,0:3,0:3,0:3} + 3 thumbs [mx] (split)
• macropad-3x3: 18 keys {0:3,0:3,0:3} [mx] (unibody)
• planck: 48 keys {0:4,0:4,0:4,0:4,0:4,0:4} [mx] (unibody)
• split-36: 18 keys {0:3,0:3,0:3,0:3,0:3} + 3 thumbs [mx] (split)
• sweep: 17 keys {0:3,0:3,0:3,0:3,0:3} + 2 thumbs [choc] (split)
• test-single-choc: 1 keys {0:1} [choc] (split)
• test-single-mx: 1 keys {0:1} [mx] (split)
• unibody-36: 36 keys {0:3,0:3,0:3,0:3,0:3} + 3 thumbs [choc] (unibody)
bun run build -- split-36Output:
Generated files for profile: split-36
• Keyboard size: 18 keys
• Plate dimensions: 123.5×114.2mm
./dist/split-36-w92ivk/
├── bottom.scad (7.9K)
├── complete.scad (50.7K)
└── top.scad (38.2K)
bun run build:stl -- split-36Output:
./dist/split-36-w92ivk/
├── bottom.scad (7.9K)
├── bottom.stl (52.8K) ← Ready to print
├── complete.scad (5.9K)
├── complete.stl (49.6K)
├── top.scad (3.5K)
└── top.stl (50.5K) ← Ready to print
bun run build -- <profile>- Outputs to
dist/<profile>-<hash>/ - Generates SCAD files only
- Each build preserved with unique timestamp
- Fast iteration for design changes
bun run build:stl -- <profile>- Outputs to
dist/<profile>-<hash>/ - Generates both SCAD and STL files
- Uses scad-js renderer internally (no OpenSCAD installation required)
- Ready for 3D printing
bun run build:dev -- <profile>- Outputs to
dist/(overwrites previous) - Watch mode: rebuilds on file changes
- Open
dist/complete.scadin OpenSCAD for live preview - Perfect for rapid iteration
Create profiles/my-keyboard.ts:
import type { ParameterProfile } from '../src/interfaces.js';
export const profile: ParameterProfile = {
layout: {
matrix: {
rowLayout: [
{ start: 0, length: 5, offset: 0 },
{ start: 0, length: 5, offset: 2 },
{ start: 0, length: 4, offset: 5 },
],
},
edgeMargin: 8.0, // Space around keys (or use { top: 3, bottom: 3, left: 4, right: 3 })
baseDegrees: 10.0, // Overall rotation
},
switch: {
type: 'choc', // or 'mx'
},
thumb: {
cluster: {
keys: 3, // Number of thumb keys
spacing: 20.0, // Space between thumb keys
rotation: 15.0, // Thumb cluster angle
},
offset: {
x: 25, // Horizontal position
y: 2, // Vertical position
},
},
connectors: [
{
type: 'usbC',
face: 'top', // top, bottom, left, or right
position: 0.5, // 0-1 along the edge
enabled: true,
clearance: 0.2,
},
],
enclosure: {
plate: {
topThickness: 1.5,
bottomThickness: 1.5,
},
walls: {
thickness: 1.5,
height: 9.0,
},
},
output: {
showSwitches: true, // Show switches in preview
showKeycaps: true, // Show keycaps in preview
keycapProfile: 'dsa', // Keycap style: 'dsa', 'xda', 'choc', or 'none'
},
};bun run build:dev -- my-keyboardThe filename (without .ts) becomes the profile name. No registration needed — profiles are auto-discovered.
Rectangular (default) — uses enclosure.cornerRadius for rounding:
enclosure: {
cornerRadius: 3, // corner rounding radius (mm)
}Organic — non-rectangular shapes that follow the key layout:
enclosure: {
outline: {
type: 'organic',
keyPadding: 1.3, // mm from key edge to inner wall surface
closingRadius: 10, // concavity control (higher = smoother concave regions)
cornerRadius: 3, // outline corner rounding
resolution: 1.0, // grid cell size in mm (smaller = more detailed)
simplifyEpsilon: 0.8, // point simplification tolerance in mm
},
}thumb: {
cluster: {
keys: 3,
spacing: 20.0,
rotation: 15.0,
},
perKey: {
rotations: [-10, 0, 10], // Individual key angles
offsets: [
{ x: 2, y: 0 }, // Fine-tune each key position
{ x: 0, y: 0 },
{ x: 2, y: 0 },
],
},
}Add reinforced sockets for silicon rubber feet:
enclosure: {
bottomPadsSockets: [
{
shape: 'round', // or 'square'
size: { radius: 5.05 },
depth: 1.1,
position: {
anchor: 'bottom-left', // corner anchor
offset: { x: 0, y: 0 } // fine adjustment
},
reinforcement: {
thickness: 1,
height: 0.2
},
},
],
}Add a built-in microcontroller pocket to the top plate:
enclosure: {
topMCUPocket: {
size: {
width: 18.5, // Pocket width
height: 33.5, // Pocket height
depth: 0.0, // Pocket depth from top surface
},
pinAccess: {
width: 12, // Center opening for pin through-holes
},
position: {
anchor: 'bottom-left', // Corner anchor
offset: { x: 9, y: -5 }, // Fine adjustment
rotation: -90, // Rotation in degrees
},
reinforcement: {
thickness: 1, // Wall thickness around pocket
height: 3.5, // Reinforcement height
},
usbPort: {
width: 11, // USB port cutout width
height: 6.5, // USB port cutout height
position: 'bottom', // Edge of pocket: top, bottom, left, right
offset: 0, // Offset along the edge
},
},
}Add weight-reducing patterns to the bottom plate:
enclosure: {
bottomPattern: {
type: 'honeycomb', // 'honeycomb', 'circles', or 'square'
cellSize: 14, // Size of each cell
wallThickness: 4, // Wall between cells
margin: 5, // Inset from case edges
},
}Patterns automatically avoid cutting through pad sockets and MagSafe ring areas.
Add a MagSafe ring socket for tenting with phone holders and magnetic mounts:
enclosure: {
magsafeRing: {
clearance: 0.2, // Fit adjustment (positive = looser)
reinforcement: {
outer: 2.0, // Thickness around outer diameter
inner: 2.0, // Grip margin on inner diameter
height: 0.5, // Additional height for ring
},
position: {
offset: { x: 0, y: 0 }, // Offset from keyboard center
placement: 'embedded', // 'external' or 'embedded'
},
},
}Standard MagSafe dimensions: 56mm outer / 44mm inner / 0.6mm depth.
connectors: [
{
type: 'usbC',
face: 'left',
position: 0.8,
enabled: true,
clearance: 0.2,
},
{
type: 'trrs',
face: 'right',
position: 0.3,
enabled: true,
clearance: 0.2,
},
{
type: 'powerButton',
face: 'top',
position: 0.1,
enabled: true,
clearance: 0.2,
},
]Control how your keyboard appears in the preview:
output: {
showSwitches: true, // Show Cherry MX switch bodies
showKeycaps: true, // Show keycaps
keycapProfile: 'dsa', // 'dsa', 'xda', 'choc', or 'none'
colors: {
topPlate: '#b54c9e', // Top plate color (hex or named)
bottomPlate: '#037da3', // Bottom plate color
keycaps: 'WhiteSmoke', // Keycap color
},
}Visualization settings only affect the complete.scad / complete.stl preview file. The top and bottom files are generated without switches or keycaps for actual printing.
- split-36 — 36-key split ergonomic (MX, MCU pocket, MagSafe, honeycomb, organic outline)
- corne — 42-key split ergonomic (MX, USB-C, MagSafe, circles pattern)
- sweep — 34-key split minimalist (Choc, USB-C, MagSafe, honeycomb)
- unibody-36 — 36-key unibody ergonomic (Choc, USB-C, power button, organic outline)
- planck — 48-key unibody ortholinear (MX, USB-C)
- macropad-3x3 — 3×3 macropad (MX, MCU pocket, honeycomb)
- test-single-choc — single Choc switch for fit testing
- test-single-mx — single MX switch for fit testing
Print the test-single-choc or test-single-mx profile first to verify your printer is tuned and switches fit snugly. Test the snap-fit mechanism between top and bottom — parts should snap together securely without excessive force.
- Leave adequate room for your microcontroller and battery
- Consider wiring thickness — adjust
walls.heightfor more internal space - Add
layout.edgeMarginfor extra room around switches - Use
build:devmode with OpenSCAD preview to verify connector clearances - Double-check connectors don't interfere with switch positions or wiring paths
- Material: PLA works, PETG recommended for durability
- Supports: the top plate needs support for switch cutouts; bottom may need support for rubber feet sockets
- Orientation: the top plate may need to be printed upside down (rotated 180 degrees)
All electronics and wiring fit in the top part of the case. The bottom snaps on and can be removed with a plastic pry tool.
top.scad/top.stl— top plate with switch mounting, electronics cavity, and optional MCU pocketbottom.scad/bottom.stl— bottom case with snap-fit walls, optional pad sockets, MagSafe ring, and bottom patterncomplete.scad/complete.stl— assembled preview with optional switch bodies and keycaps
- TypeScript — configuration and geometry logic
- scad-js — TypeScript-to-OpenSCAD transpiler (also renders STL directly)
- fp-ts — functional programming patterns
- Bun — runtime and build system
- Biome — linting and formatting
bun run build -- <profile> # Generate SCAD files
bun run build:dev -- <profile> # Watch mode (live preview)
bun run build:stl -- <profile> # Generate STL files (3D printing)
bun run list # List all keyboards
bun run help # Show help
bun run clean # Remove generated files
bun run check # Lint and format (Biome)- Web UI — browser-based visual configurator
- Multi-row thumb clusters — complex thumb layouts with multiple rows
- Trackpad/trackpoint support — integrated pointing device mounting
- Screw standoffs — alternative to snap-fit assembly
Add your keyboard profiles — create a .ts file in profiles/ and submit a PR.
For custom switch types or connectors, add them to src/switches.ts or src/connector-specs.ts.
MIT