---
title: SC Typeface Card
description: One specimen card per weight and style of a typeface.
url: https://secretocho.design/lab/sc-typeface-card/
type: SoftwareSourceCode
status: working
published: 2026-10-05
---

# SC Typeface Card

> One specimen card per weight and style of a typeface.

component 17 / 28 · WORKING

- **type**: Code Component
- **platform**: Framer
- **license**: SecretoCho Design

![A still of the SC Typeface Card demo, as its module draws it with reduced motion.](https://secretocho.design/_astro/still.BuEzcP0r_Z2gqzPY.webp)

*fig 00 · live demo, ported from **SCTypefaceCard.tsx** · starts on Recursive, since the default face comes from a font kit this site does not load*

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

- typography
- specimen
- type card
- font weights
- design system
- clipboard
- json
- character set

### 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

1. Drop **SCTypefaceCard** into a frame.
2. Paste **Font Data**: {"fontFamily": "…", "importUrl": "…" (optional), "variants": \[{"name", "weight", "style"}, …\]}. Keys must be quoted.
3. Choose the **Sample Text** mode: inline, lines, paragraph or custom; toggle uppercase, lowercase, numbers, punctuation and special characters.
4. Set **Display Settings** (sample size, line height, letter spacing, character sets) and **Layout** (columns, gap, radius, breakpoint).
5. Style the header, label, meta and fallback fonts; pick a shadow style; set the footer and notification colours.
6. 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

### Header comment

SCTypefaceCard.tsx has no doc comment.

[props \[REF\]](#reference)

### 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.

*SCTypefaceCard.tsx · lines 275–295 · tsx*

```tsx
    // 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

- [Creative coding](https://secretocho.design/about/#capabilities)

## 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](mailto:sc@secretocho.design)

[read the CV](https://secretocho.design/cv/) · [LinkedIn](https://www.linkedin.com/in/secretocho/)
