@lukewarmraven/figma-scale
v0.2.0
Published
Figma px → responsive CSS calc utility. Maps design canvas dimensions to live viewport scale via CSS custom properties.
Maintainers
Readme
@lukewarmraven/figma-scale
Converts Figma design canvas px values into responsive CSS calc() strings that scale with the live viewport — so your designs stay proportional at any screen size without media query breakpoints.
Works in any JS/TS project: Next.js, React, Vue, Vite, vanilla JS, etc.
Recommended: fluid, zoom-friendly sizing with clamp()
For text and spacing that scale across screen sizes and respect browser zoom, pair rem() with CSS clamp(). rem() returns your Figma px value as a rem string, and you compose it inside clamp(min, preferred, max):
import { rem } from '@lukewarmraven/figma-scale'
// Flows between 16px and 32px, centered on the Figma value (24px).
// The rem terms keep it zoom-friendly; 0.5vw adds viewport responsiveness.
<h1 style={{ fontSize: `clamp(${rem(16)}, ${rem(24)} + 0.5vw, ${rem(32)})` }}>
Hello
</h1>Why
rem+clampinstead ofvw/vh?vw/vhlock to the viewport, so browser zoom won't rescale them (elements stay the same proportion of the screen).remis relative to the root font-size, so zoom works — andclamp()gives smooth fluid scaling between bounds.
Examples: fonts, elements & spacing
Everything you pull from Figma is a px number. The three things you'll convert most are text, box sizes, and gaps — below each is shown in both rem (+ clamp) and vw/vh forms.
| When you need… | Use |
|---|---|
| Sizing that respects browser zoom & stays fluid | rem() + clamp() |
| Proportions locked to the viewport (like the Figma canvas) | vw() / vh() |
Fonts
import { rem, vw } from '@lukewarmraven/figma-scale'
// rem + clamp — fluid between 16px and 32px, zoom-friendly
<h1 style={{ fontSize: `clamp(${rem(16)}, ${rem(24)} + 0.5vw, ${rem(32)})` }}>Title</h1>
// vw — locks to viewport width, keeps Figma proportions at any size
<p style={{ fontSize: vw(24) }}>Body</p>Elements (width / height)
import { rem, vw, vh } from '@lukewarmraven/figma-scale'
// rem — stable, zoom-friendly box
<div style={{ width: rem(320), height: rem(200) }} />
// vw / vh — scales with the viewport, matching the Figma canvas
<div style={{ width: vw(320), height: vh(200) }} />Spacing (padding / margin / gap)
import { rem, vw } from '@lukewarmraven/figma-scale'
// rem + clamp — fluid spacing that respects zoom
<section style={{ padding: `clamp(${rem(16)}, ${rem(32)} + 1vw, ${rem(64)})` }} />
// vw — viewport-locked spacing
<section style={{ padding: vw(32), gap: vw(24) }} />Install
npm install @lukewarmraven/figma-scaleHow it works
Call initFigmaScale() once at your app's entry point. It sets two CSS custom properties on :root:
--figma-scale-w=window.innerWidth / canvasW(updates on resize)--figma-scale-h=window.innerHeight / canvasH(updates on resize)
Then vw(px) and vh(px) return calc() strings that multiply your Figma px value by those variables:
vw(200) → calc(200px * var(--figma-scale-w) * 0.6)The element's rendered size scales proportionally with the viewport — matching your Figma design at the exact canvas dimensions, and shrinking/growing smoothly at any other size.
Zoom-friendly alternative:
rem(px)converts a Figma px value to aremstring (relative to root font-size) instead of the viewport. It respects browser zoom — pair it with aclamp()in your project for fluid sizing that still responds to zoom.
Usage
Vanilla JS / any framework
Call initFigmaScale in your entry file, then use vw / vh anywhere:
// main.ts / index.ts
import { initFigmaScale } from '@lukewarmraven/figma-scale'
initFigmaScale({
canvasW: 1728, // your Figma canvas width
canvasH: 1117, // your Figma canvas height
scale: 0.6, // global multiplier — tune if elements look too big/small
})// any component / module
import { vw, vh } from '@lukewarmraven/figma-scale'
element.style.width = vw(200) // "calc(200px * var(--figma-scale-w) * 0.6)"
element.style.height = vh(100) // "calc(100px * var(--figma-scale-h) * 0.6)"
element.style.fontSize = vw(24)React / Vite
// src/main.tsx
import { initFigmaScale } from '@lukewarmraven/figma-scale'
initFigmaScale({ canvasW: 1440, canvasH: 900, scale: 0.55 })// src/components/Hero.tsx
import { vw, vh } from '@lukewarmraven/figma-scale'
export function Hero() {
return (
<div style={{ width: vw(800), height: vh(400) }}>
<h1 style={{ fontSize: vw(64) }}>Hello</h1>
</div>
)
}Next.js (App Router)
Next.js renders on the server, so the init script must run as an inline <script> before first paint. Use getScriptString() for this — it returns a self-contained IIFE with your config baked in.
// app/layout.tsx
import { getScriptString } from '@lukewarmraven/figma-scale'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: getScriptString({ canvasW: 1728, canvasH: 1117, scale: 0.6 }),
}}
/>
</head>
<body>{children}</body>
</html>
)
}// anywhere in your components (client or server)
import { vw, vh } from '@lukewarmraven/figma-scale'
export function Card() {
return (
<div style={{ width: vw(320), height: vh(200), fontSize: vw(16) }}>
...
</div>
)
}Note:
initFigmaScale()is not needed in Next.js when you usegetScriptString()in the layout — the inline script handles the runtime setup. Importvw/vhdirectly in your components.
Next.js (Pages Router)
// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
import { getScriptString } from '@lukewarmraven/figma-scale'
export default function Document() {
return (
<Html>
<Head>
<script dangerouslySetInnerHTML={{ __html: getScriptString() }} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}Config options
All fields are optional. Defaults are shown.
| Option | Type | Default | Description |
|---|---|---|---|
| canvasW | number | 1728 | Width of your Figma design canvas in px |
| canvasH | number | 1117 | Height of your Figma design canvas in px |
| scale | number | 0.6 | Global multiplier on top of the viewport scale. Decrease if elements look too big, increase if too small |
| fallbackW | number | 1280 / canvasW | Value for --figma-scale-w before the script runs (SSR / first paint). Auto-computed if not set |
| fallbackH | number | 800 / canvasH | Value for --figma-scale-h before the script runs (SSR / first paint). Auto-computed if not set |
API
// Sets up CSS vars + resize listener. Call once at app entry.
// Not needed in Next.js if you use getScriptString() in the layout.
initFigmaScale(config?: FigmaScaleConfig): void
// Convert a Figma px value using the horizontal scale.
vw(px: number): string
// Convert a Figma px value using the vertical scale.
vh(px: number): string
// Convert a Figma px value to a rem string. Zoom-friendly alternative to vw/vh.
// base = root font-size in px (default 16; use 10 for the 62.5% trick).
rem(px: number, base?: number): string
// Returns a self-contained inline script string for SSR frameworks.
// Config values are baked in as literals — no runtime imports needed.
getScriptString(config?: FigmaScaleConfig): stringCSS fallback (optional)
If you want fallback values in CSS instead of relying on the script, add this to your global stylesheet. Replace the values with 1280 / yourCanvasW and 800 / yourCanvasH:
:root {
--figma-scale-w: 0.7407; /* 1280 / 1728 */
--figma-scale-h: 0.7162; /* 800 / 1117 */
}This is handled automatically by initFigmaScale and getScriptString, so you only need this if you're managing CSS variables yourself.
License
MIT © Raven Luke Quinto
