Public
Design & print employee ID badges with photo + logo uploads
design-toolhonointernal-toolsreact
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Design employee ID badges in the browser — upload a headshot and your company logo, pick a layout and colors, then print on standard CR80 card stock or export a PNG.
- Image uploads — drag-and-drop (or click) a photo and a company logo. Both
are downscaled in the browser with a canvas before upload, then stored as
blobs and served from
/img/:filewith immutable caching. - Three layouts — Classic (header bar + round photo), Modern (full-bleed color panel), Minimal (type-led). Each renders in portrait or landscape.
- Custom colors — six presets plus accent / card / text color pickers.
- True-to-life sizing — cards are laid out at 2.125in × 3.375in (CR80), so printing at 100% gives a badge that fits a standard holder.
- Roster — save badges to SQLite, reload them for editing, and print the whole roster as one sheet.
- PNG export — renders the preview at 4× with
html-to-image.
Rendering mermaid diagram...
index.ts ← Hono server: pages, upload + roster APIs
backend/
uploads.ts ← image upload to blob storage, /img/:file serving
badges.ts ← saved-badge CRUD in SQLite
shared/
types.ts ← Badge shape, templates, presets, card sizing
frontend/
root.tsx ← HTML shell (Twind + the @media print rules)
index.tsx ← React entrypoint
lib/images.ts ← canvas downscaling + upload helper
components/
App.tsx ← layout, save/print/export actions
Controls.tsx ← the design form
BadgeCard.tsx ← the badge itself (3 templates × 2 orientations)
ImageUpload.tsx ← drag-and-drop upload widget
Roster.tsx ← saved badge list
Print rules live in frontend/root.tsx as the app's only stylesheet — @page
margins and print-only visibility can't be expressed as utility classes. When
printing, enable Background graphics in the browser's print dialog so accent
colors render, and print at 100% scale (no "fit to page") for accurate card
dimensions.
Photos and logos are blobs; on the free plan all blobs share a 10MB budget, which is why uploads are downscaled to a 640px (photo) / 480px (logo) long edge first. Removing a badge from the roster does not delete its uploaded images.