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 (×ばつ16.6mm spacing)
- Cherry MX — standard mechanical switches (×ばつ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 git@github.com:20lives/flatboard.git
cd flatboard
bun installbun run list
Output:
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-36
Output:
Generated files for profile: split-36
• Keyboard size: 18 keys
• Plate dimensions: ×ばつ114.2mm
./dist/split-36-w92ivk/
├── bottom.scad (7.9K)
├── complete.scad (50.7K)
└── top.scad (38.2K)
bun run build:stl -- split-36
Output:
./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-keyboard
The 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 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