Framed codes, explained
A framed code is the one KeepsakeCodes style in which the QR code itself is not touched. The engine renders an ordinary code, nests that markup unchanged on an opaque white panel, and draws the decoration — one of 5 frame templates, each recolourable — beneath and around it. This guide walks through that construction with figures taken from the engine when the page was built: the geometry, the palette, the caption, the occasions that start on a frame, and what verification proves. Every example is fictional and was rendered by the same code that powers the generators. The shared rules are on the methodology page; other guides are at /guides/.
What a framed code is
The two styles that redraw modules begin by requiring a model encoded at error correction level H, which keeps roughly 30% recovery in reserve: hearts redraw its modules, halftone lets a photo into them, and both spend that reserve on decoration. A framed code alters nothing: it takes the finished SVG of a plain code, quiet zone included, and copies it verbatim into a larger drawing. Because it spends no margin, the engine's rule for a frame is looser — it accepts a model at level Q as well as H — and the section on the encoding rule below demonstrates both edges.
The templates are heart, floral, ring, classic, confetti. Each is vector art painted from a palette of primary, secondary and accent colours, and the order of painting is the whole mechanism. The frame art is painted first. An opaque white panel is painted over it, its 2-unit outline in the palette's secondary colour. The code is placed on the panel, and a caption is set beneath. Because the panel is opaque and painted after the art, nothing in the frame can show through under a module; because the code markup is copied in unchanged, nothing in the frame can change a module.
A framed example, rendered by the engine
A fictional link in the classic frame — the address does not exist — rendered when this page was built. It encodes:
https://example.com/anniversary/ten-years
Version 5 at 37×37 modules, error correction H; 45×45 modules once the 4-module quiet zone is counted. The composite is 760×880 units; the panel is 460 units square at (150, 240), and the code with its quiet zone fills 57% of the artwork's width.
Three facts about this render were checked when the page was built. The plain code's markup appears inside the composite byte for byte: yes. The panel is painted after the frame art and before the code: yes. All 5 templates share the same 760×880 canvas and the same 460-unit panel, and every one of them nests this code unchanged: yes. If any of those answers had been "no", this page would not have built.
Why the code is never modified
The anatomy rule that governs the other styles — finder and alignment patterns stay classic squares because restyling alignment patterns broke decoding in testing — is satisfied trivially here: no module is restyled. What the frame protects instead is the margin. The code and its quiet zone occupy 57% of the artwork's width and the panel 61%, so the nearest frame art sits outside the quiet zone with white to spare.
The composite enforces one encoding rule of its own, and it is a different rule from the one hearts and halftone obey. Handed the model the code was rendered from, it accepts level Q or H and refuses anything lower, even though it will not alter a single module. Re-encoded at level M, the same fictional link was refused with: Framed keepsakes require error correction level Q or H — re-encode with ecLevel "H". Re-encoded at level Q it was accepted — yes — and nested verbatim as a version 4 symbol of 33×33 modules, against version 5 at H for the example above: 1 version smaller for this link. The engine's reason is that a frame spends no margin on decoration, so the code inside it is an ordinary QR code and a lighter level buys back versions, which is real millimetres on a tag. The generator offers this as an opt-in smaller code, off by default; it is not offered with heart modules, because the heart renderer handed the same Q model refuses it: Artistic styles require error correction level H — re-encode with ecLevel "H". Whichever level is chosen, the render still has to pass the same decode verification before it can be downloaded. The worked examples in this guide and on the occasion pages are all encoded at H; the table below checks that.
Two other refusals mark the edges. Ask for a template that does not exist and the engine answers: Unknown frame template “lace”. Use one of: heart, floral, ring, classic, confetti. Hand it a palette colour that is not a hex value and it answers: Palette primary must be a hex color like #be123c (got “teal”).
Recolourable art, and what recolouring cannot do
A palette's fields are primary, secondary and accent. Each template spends them differently, but the engine validates every field the same way, as a hex colour and nothing more. There is no contrast check on frame colours, and none is needed: the frame is never under a module.
The classic frame above was rendered a second time with a teal-and-orange palette. The artwork changed: yes. The nested code stayed byte-identical: yes. The panel's fill stayed #ffffff: yes; its 2-unit outline took the new secondary colour, #99f6e4: yes. What the palette cannot reach is the code's own colours: in the framed pipeline the code is rendered dark-on-white on an opaque white panel, a pair the engine measures at 21.00:1, well above the 4.5:1 threshold every artistic style requires.
The caption: words in the ink
Beneath the panel the frame sets a caption of up to 200 characters across at most 4 lines. The limit exists so that a whole message can be printed under the code: a code that fails to scan, or is scanned in fifty years by a device nobody has imagined yet, still delivers its words because they are in the ink. A short caption sets on one line; a long one steps down a size and wraps. The renderer cannot measure text, so line breaks come from a character budget derived from the font size, and a word longer than a line is broken rather than allowed to overflow the frame.
Measured on this page's examples: the caption "Ten years at one table" set on 1 line; a fictional 176-character message set on 4 lines and was clipped with an ellipsis, because content beyond line 4 is dropped and the final line is marked so a clipped caption never reads as the whole message. A caption of 201 characters is refused: Captions are limited to 200 characters.
Which occasions start on a frame
7 of the 9 occasion presets open on a framed style. The table is generated from the preset registry at build time, so it cannot disagree with the tool pages it links to.
| Occasion | Style | Carries | Modules | Default primary | Example symbol |
|---|---|---|---|---|---|
| Proposal | Heart frame | a link | heart-shaped | #be123c | v4, 33×33, EC H |
| Wedding | Floral frame | a link | plain | #15803d | v5, 37×37, EC H |
| Baby reveal | Confetti frame | message text | plain | #be123c | v6, 41×41, EC H |
| Gender reveal | Confetti frame | message text | plain | #7c3aed | v6, 41×41, EC H |
| Gift tag | Classic frame | message text | plain | #b91c1c | v8, 49×49, EC H |
| Treasure hunt | Ring frame | message text | plain | #a16207 | v8, 49×49, EC H |
| Memorial | Classic frame | a link | plain | #1f2937 | v5, 37×37, EC H |
3 of these presets encode a link and 4 encode message text, and every worked example rendered for their pages is encoded at H: yes. "Carries" is the payload type the preset starts on.
Hearts inside a frame
Proposal is the framed preset that nests a heart-module code rather than a plain one. The heart render's markup is copied verbatim onto the panel, confirmed at build time: yes, but the inner code now carries the heart style's own measured rules. Each dark module outside the finder and alignment patterns is redrawn as a heart spanning 0.97 of its cell, and foreground and background must measure at least 4.5:1 in WCAG contrast; the engine rejects weaker pairs rather than warning.
The contrast gate is the heart renderer's rule, and it is demonstrated here on the engine
directly. Inside a frame the hearts are rendered with no colour chosen, so the renderer's own
default foreground applies: #881337, read back from the render, which it
measures at 9.57:1 on white and passes. A pale pink such as
#f9a8d4 measures 1.81:1 and is refused with:
Artistic styles require at least 4.5:1 contrast (got 1.81:1) — decoration already spends decode margin. It is the module colour, not the frame colour, that can fail. A heart
colour of your own is a choice the bare heart-module style offers — this preset's default there is
#881337 — and the
heart-module guide covers the rest of that style.
What a phone does with a framed message
A frame changes nothing about what the code carries. A QR code containing bare text carries no scheme, so a phone has nothing to act on. Apple's Camera and Google's ML Kit recognise links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins — and no plain-text type. Faced with bare text, an iPhone offers to copy or search it and Google Lens runs a web search; Samsung phones and dedicated scanner apps do display it, but a keepsake cannot depend on which phone opens it.
Both honest modes apply. A reveal link encodes a normal URL to a page on this site with the message in the URL's fragment, which is stripped before any request is made and never reaches a server. An offline note writes the words into the code itself and accepts the display caveat. In either mode the frame adds its caption area, where the message is set in the ink up to the 200-character limit. The message keepsakes guide works through the fragment format with a computed example.
Verification, and what it cannot promise
A framed render is verified as a whole, not as a bare code. After each render the browser rasterises the finished artwork — frame art, panel, code and caption — at 800, 1200, 1600 px, runs a real decoder (jsQR) on each raster, and compares the decoded bytes to the exact content. The "Verified scannable" badge is earned when at least 2 of the 3 rasters match; anything less shows a warning and keeps the downloads locked. Several sizes are used because a render can fail at exactly one raster size through sampling resonance and decode at the others. The decode verification guide explains the rule in full.
Verification proves that this artwork decodes cleanly at screen resolution. It cannot see your printer, your paper, or the phone that will do the scanning, and a frame does not change that.
Printing and testing a framed code
The methodology's print guidance is stated for the code: print at least 2 × 2 cm for arm's-length scanning — or the engine's readout, when that is larger — keep the quiet zone uncropped, and scan a test print with a real phone before the moment that matters. The engine's readout is computed per code: a module must print at least 0.5 mm, so this example's 45 modules across, quiet zone included, give a floor of 22.5 mm for the code itself. A frame scales that floor by geometry: the code and its quiet zone occupy 57% of the artwork's width, so the framed artwork must print at least 39.6 mm wide for the code inside it to reach the engine's floor. The printing guide carries the module-size arithmetic behind that floor.
Common misreadings
- Cropping into the panel. The 4-module quiet zone is inside the panel, not outside it. Trimming off the frame art leaves the plain code and its quiet zone intact; trimming into the white around the modules removes the margin the decoder needs.
- Expecting bare text to display. A frame does not turn message text into something a phone acts on. A reveal link opens as a page; an offline note accepts the caveat above; in both cases the caption carries the words.
- Low-contrast heart colours. Only the bare heart-module style offers a module colour
to choose; hearts inside a frame take the renderer's default,
#881337. Whichever way the colour is set, the heart renderer gates it at 4.5:1. Frame colours are not gated, because they never sit under a module.
Framed code FAQ
Does recolouring the frame change whether the code scans?
No. The palette's fields — primary, secondary and accent — are each checked only as a hex colour, and none of them touches the code. At build time a recoloured classic frame produced different artwork and the same nested code markup, byte for byte, and verification still runs on the whole artwork.
Can I crop a framed keepsake down to just the code panel?
The panel holds the code and its 4-module quiet zone, so trimming away the frame art leaves the plain code and its quiet zone intact. Cutting into the panel removes quiet zone, and the methodology's rule is to keep the quiet zone uncropped.
Why is the panel white, and can I change it?
In this engine's framed pipeline the panel's fill is always opaque white — read back from the render as #ffffff — and the code is drawn dark-on-white, a pair the engine measures at 21.00:1. The fill is not recolourable; it exists to guarantee a clean background under every module. Only the panel's 2-unit outline follows the palette, taking its secondary colour: #9ca3af in the classic frame's default palette, #99f6e4 in this guide's recoloured example.
Will a phone show the message inside a framed code?
A code containing bare text carries no scheme, so a phone has nothing to act on: Apple's Camera and Google's ML Kit recognise links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins, and no plain-text type. An iPhone offers to copy or search the text and Google Lens runs a web search; Samsung phones and dedicated scanner apps do display it. A reveal link avoids this by encoding a normal URL with the message in its fragment, and the caption sets the words in the ink either way, up to 200 characters.
Which occasions start on a frame?
7 of the 9 occasion presets: Proposal (heart), Wedding (floral), Baby reveal (confetti), Gender reveal (confetti), Gift tag (classic), Treasure hunt (ring), Memorial (classic). Proposal nests heart-shaped modules inside the frame; the others nest a plain code.
Is a framed code safer than a heart-module or halftone code?
By construction, yes: the other two styles redraw modules and spend error-correction margin on decoration; a frame leaves every module as the encoder produced it. It is still verified the same way, at 800, 1200, 1600 px with 2 of 3 rasters required to match before the downloads unlock.
Every render is decode-verified in your browser before it can be downloaded. The complete rule set, for every style, is on the methodology page; the other guides are listed at /guides/.