component 17 / 28WORKING
SC Typeface Card
- type
- Code Component
- platform
- Framer
- license
- SecretoCho Design
demo · runs with JavaScript
Typeface specimen cards from JSON: one card per weight and style, sample text, character sets, CSS footer with copy-to-clipboard.
SCTypefaceCard turns a typeface’s variants into a grid of specimen cards. Paste a JSON object with the family name, an optional stylesheet URL and the list of weights and styles; every variant gets a card with its name and weight badge, a sample line set in that weight and style, optional uppercase / lowercase / numerals character sets, and a footer with the CSS values and a Copy CSS button.
Built for design-system pages: the specimen text, sizes, colours, shadows and the four UI fonts are all property controls.
- Design System
- Typography
- UI
Main
Overview
- Built With
- React (hooks), CSS (inline), ResizeObserver, Clipboard API, Framer Code Component API
- Layout Support
- CSS grid, 1–4 columns; one column at or under the breakpoint when Responsive is on
- Orientation
- Vertical cards: header, sample, optional character sets, footer
- Ideal Use Case
- Type specimens in brand guidelines, design-system typography pages, font pairing reviews
Tags
Code comment
SCTypefaceCard.tsx has no doc comment.
Dependencies
- Technologies
- None beyond React and the Framer runtime
- Notes
- Injects a <link rel="stylesheet"> for the importUrl in the font data; the sample is set in that family with the Sample Font Fallback behind it.
Complexity
- Logic Level
- Medium (JSON validation, font loading, sample-text assembly, three shadow styles, toast)
- Runtime Cost
- Low (no animation loop; one ResizeObserver; one stylesheet link per family)
- Component Size
- ~63 KB TSX (63,382 bytes, 1,499 lines)
- Notes
- About two thirds of the file is the property-control tree.
Guide
Instructions
- Drop SCTypefaceCard into a frame.
- Paste Font Data: {"fontFamily": "…", "importUrl": "…" (optional), "variants": [{"name", "weight", "style"}, …]}. Keys must be quoted.
- Choose the Sample Text mode: inline, lines, paragraph or custom; toggle uppercase, lowercase, numbers, punctuation and special characters.
- Set Display Settings (sample size, line height, letter spacing, character sets) and Layout (columns, gap, radius, breakpoint).
- Style the header, label, meta and fallback fonts; pick a shadow style; set the footer and notification colours.
- Keep Show Parse Errors on while editing the JSON.
Examples / Presets
Default
The source defaults: the built-in PF Marlet Display data (12 variants), inline sample, 2 columns, footer with Copy CSS.
Recursive
- fontDataJSON
- Recursive (weights 300–1000, two italics, no importUrl)
- display.showCharsets
- true
Header only
- visibility.showBody
- false
- footer.showFooter
- false (the variant name, weight and badge only)
Site style
- layout.columns
- 3
- layout.gap
- 10
- layout.borderRadius
- 0
- display.showCharsets
- true
- footer.showFooter
- true
“Recursive” and “Site style” use the demo module’s own font data and UI fonts (the site’s typeface); the props listed here are the subset that the source’s controls name.
Mechanics
How it Works
- Data: parseFontData validates fontFamily and the variants array (name, weight, style per item) and falls back to the built-in data with a message when the JSON is wrong.
- Font loading: when importUrl is set, an effect appends one <link rel="stylesheet"> with the id typeface-import-<family> to <head>, once per family; its onload sets fontLoaded, which nothing reads. Without importUrl the family is assumed available.
- Sample: Custom Text wins when set; otherwise the enabled character groups are joined with spaces (inline) or newlines (lines), or the pangram paragraph is used.
- Cards: the header strips the family name from the variant name, shows Weight and an Italic tag, and a badge with the weight’s name (Hairline … Black). The sample is set in "<family>", fallback at the variant’s weight and style.
- Footer: shows font-weight and font-style as code chips; Copy CSS writes font-family / font-weight / font-style to the clipboard and raises a toast.
- Shadows: standard (hover multiplies offset ×4 and blur ×3), neumorphism (two shadows, optional inset on hover) or custom strings.
Performance & Tuning
- One card per variant: twelve variants render twelve samples plus optional character sets; keep Max Height set when the sample is long.
- Only one stylesheet link is injected per family, and it stays in the document after unmount.
- Hover state lives in the parent, so hovering re-renders the grid; fine for a specimen page, avoid hundreds of variants.
Compatibility & Limits
- Evergreen browsers; needs ResizeObserver and navigator.clipboard (secure context).
- The stylesheet host must allow the font on your domain (web-font services check referrers / kits).
- Weights are rendered with font-weight; browsers fake only bold and oblique; a missing lighter weight falls back to the nearest available one.
Quality
Accessibility
- The variant name is an <h3>; character-set labels are <label> elements without a control (decorative labels).
- Copy CSS is a real <button>; the toast is visual only.
- Meta text is 10–12 px uppercase: check contrast of secondaryText / labelText on your card background.
Security & Privacy
- Network: one stylesheet request to whatever importUrl the Font Data holds; the built-in default points at an Adobe Fonts (Typekit) kit URL. No fetch of its own, no tracking.
- Clipboard: navigator.clipboard.writeText on the Copy CSS click.
- No storage, no permissions. Font Data goes through JSON.parse only; nothing is evaluated or injected as HTML.
- The injected <link> element is created with createElement and an id; its href is the importUrl string as given.
Reference
Props
| name | type | default | description |
|---|---|---|---|
fontDataJSON | string | the built-in PF Marlet Display data (fontFamily, importUrl, 12 variants) | Font Data. Paste valid JSON with fontFamily, importUrl, and variants array. |
showParseError | boolean | true | Show Parse Errors. Display an error message if JSON parsing fails. |
visibility | object | showHeader=true, showBody=true | Visibility: header and body content. |
sampleText | object | displayMode=inline, uppercase=true, lowercase=true, numbers=true, punctuation=true, special=false | Sample Text. Mode inline | lines | paragraph | custom; the five toggles hide in paragraph and custom modes. |
customSampleText | string | "" | Custom Text. Override default sample text (shown when the mode is custom; a non-empty value is used in every mode). |
layout | object | columns=2, gap=30, borderRadius=12, borderColor=rgba(0,0,0,0.08), responsive=true, breakpoint=768 | Layout. Columns 1–4, gap 0–100 px, radius 0–30 px, breakpoint 300–1200 px. |
spacing | object | headerPadding=20px 25px, contentPadding=30px 25px | Spacing. CSS padding strings for header and content. |
display | object | fontSize=32, lineHeight=1.3, letterSpacing=normal, sampleBg=transparent, showCharsets=false, charsetFontSize=18, charsetMobileFontSize=14, minHeight=auto, maxHeight=none | Display Settings. Sample size 12–120 px; character sets with desktop and mobile sizes; min/max height strings. |
headerFont | FontStyle | Framer font control | Header Font. The variant name. |
labelFont | FontStyle | Framer font control | Label Font. Character-set labels. |
sampleFont | FontStyle | Framer font control | Sample Font Fallback. The family behind the specimen’s own family. |
metaFont | FontStyle | Framer font control | Meta Font. Weight and Italic tags. |
shadows | object | shadowStyle=standard, enableShadow=true, shadowColor=rgba(0,0,0,0.04), shadowHoverColor=rgba(0,0,0,0.1), shadowOffsetX=0, shadowOffsetY=2, shadowBlur=10, neuLightShadow=#ffffff, neuDarkShadow=#d1d1d1, neuDistance=20, neuBlur=60, neuInset=false, customShadow=0 2px 10px rgba(0,0,0,0.04), customHoverShadow=0 8px 30px rgba(0,0,0,0.1), shadowTransition=0.3s ease | Shadows. Standard, neumorphism or custom; each style shows its own sub-controls. |
colors | object | cardBg=#ffffff, cardHoverBg=#fafafa, headerBg=#f8f8f8, footerBg=#f8f8f8, primaryText=#1a1a1a, secondaryText=#666666, labelText=#999999, accentColor=#0066cc, borderColor=rgba(0,0,0,0.08), dividerColor=rgba(0,0,0,0.06), badgeBg=#e8e8e8, codeBg=rgba(0,0,0,0.04) | Colors. Backgrounds, text roles, divider, badge and code chip (see the note under this table). |
footer | object | showFooter=true, padding=15px 25px, showButton=true, buttonColor=#999999, buttonHoverColor=#666666 | Footer. Visibility, padding, Copy CSS button and its dashed border colours. |
notifications | object | duration=3000, successBg=#d4edda, successText=#155724, errorBg=#fff3cd, errorText=#856404 | Notifications. Toast duration 1000–10000 ms and colours. |
colors.borderColor is declared but never read: the card border uses layout.borderColor. display.fontSize is a number control whose render fallback is the string "32px"; both work because React treats a bare number as px.
Notes & Tips
- The header removes the fontFamily string from a variant name only where the name contains it exactly (case-sensitive); the built-in data’s family is a lower-case CSS name, so its full variant names show.
- Leave importUrl empty for fonts Framer already loads; the cards always render at once (fontLoaded is set but never read).
- Turn on Show Character Sets for specimen pages; use Charset Mobile Size for narrow cards.
- Custom Text applies whenever it is non-empty, even if the mode is not custom.
Troubleshooting / FAQ
Samples render in the fallback font
The importUrl did not load (blocked kit, wrong domain) or the family name differs from the stylesheet’s; check the Network panel for the stylesheet.
The built-in PF Marlet Display cards appear instead of mine
The JSON failed validation: fontFamily missing, variants not an array, or a variant without name / weight / style. Show Parse Errors tells you which.
Copy CSS does nothing
The clipboard API needs HTTPS and a real click.
All cards look the same weight
The stylesheet does not include that weight: browsers fake only bold, and a missing lighter weight falls back to the nearest one; load every weight listed in variants.
Source
Key excerpt
The card loads the typeface itself: one <link rel=stylesheet> per family, given an id and appended to <head> once, reused when it already exists; no import URL means the font is treated as already available.
// Load external font if URL is provided useEffect(() => { if (fontData.importUrl) { const linkId = "typeface-import-" + fontData.fontFamily let link = document.getElementById(linkId) if (!link) { link = document.createElement("link") link.id = linkId link.rel = "stylesheet" link.href = fontData.importUrl document.head.appendChild(link) link.onload = () => setFontLoaded(true) } else { setFontLoaded(true) } } else { setFontLoaded(true) } }, [fontData.importUrl, fontData.fontFamily])Credits
Credits
© SecretoCho Design
- license
- SecretoCho Design
SecretoCho Design
Disclaimer
Provided “as is” for visual/UX purposes. Validate performance, accessibility, and licensing within your project.
Proves
Hiring a design engineer?
I’m open to senior design engineer roles: remote from Belgrade, or relocation with visa sponsorship. Write to me, or read the CV first. Client work stays selective, one project at a time.
sc@secretocho.design