Iframes are fixed-size windows into another page. If you embed a desktop site into a 400px-wide container, you don't get a responsive preview. You get a cropped mess. The content renders at 400px and your carefully designed desktop layout is gone.
What you actually want is the iframe to render at, say, 1220px, but visually shrink to fit whatever container it's in. Like a browser DevTools device preview, but embeddable anywhere.
The trick is transform: scale(). Render the iframe at its full intended width, then scale it down to fit the parent container. The content thinks it's on a 1220px screen. The user sees it neatly fitting inside a card.
The core idea
You need two things: the iframe's actual width (the viewport you want to simulate) and the parent container's width (the space you have). Divide one by the other and you get your scale factor.
const scale = containerWidth / iframeWidth
A 1220px iframe inside a 610px container gets scale(0.5). The content renders at full size but displays at half size. Every breakpoint, every media query, every layout decision works exactly as if the user had a 1220px-wide browser.
The height needs adjusting too. When you scale an element down, its visual height shrinks, but the container doesn't know that. You need to set the iframe's height to containerHeight / scale so the scaled-down version fills the container correctly.
React implementation
interface ResponsiveIframeProps {
src: string
width: number
}
const ResponsiveIframe = ({ src, width }: ResponsiveIframeProps) => {
const iframeRef = useRef<HTMLIFrameElement>(null)
const resize = useCallback(() => {
const iframe = iframeRef.current
const wrapper = iframe?.parentElement
if (!iframe || !wrapper) return
const wrapperWidth = wrapper.offsetWidth
const iframeWidth = iframe.offsetWidth
const scale = wrapperWidth / iframeWidth
iframe.style.transform = `scale(${scale})`
const wrapperHeight = wrapper.offsetHeight
const height = wrapperHeight / scale
iframe.style.height = `${height}px`
}, [])
useEffect(() => {
resize()
window.addEventListener('resize', resize)
return () => window.removeEventListener('resize', resize)
}, [resize])
return (
<iframe
ref={iframeRef}
src={src}
style={{ width, transformOrigin: 'top left' }}
/>
)
}
The transformOrigin: 'top left' is important. Without it, the scaled iframe floats in the center of its original bounding box and everything looks broken.
Adding viewport controls
Once you have the component, switching between viewports is just changing the width prop. The iframe re-renders at the new width, the resize function recalculates the scale, and the content adapts.
const [viewportWidth, setViewportWidth] = useState<number>(1220)
return (
<div>
<ResponsiveIframe src="https://baraa.app" width={viewportWidth} />
<div className="buttons">
<button onClick={() => setViewportWidth(375)}>Mobile</button>
<button onClick={() => setViewportWidth(768)}>Tablet</button>
<button onClick={() => setViewportWidth(1220)}>Desktop</button>
</div>
</div>
)
Without React
Same concept, less ceremony. Set the iframe width in CSS, calculate the scale in JS, listen for resize events.
<div class="iframe-wrapper">
<iframe id="iframe" src="https://baraa.app"></iframe>
</div>
const iframe = document.getElementById('iframe')
const resize = () => {
const wrapper = iframe.parentElement
if (!wrapper) return
const wrapperWidth = wrapper.offsetWidth
const iframeWidth = iframe.offsetWidth
const scale = wrapperWidth / iframeWidth
iframe.style.transform = `scale(${scale})`
const wrapperHeight = wrapper.offsetHeight
const height = wrapperHeight / scale
iframe.style.height = `${height}px`
}
resize()
window.addEventListener('resize', resize)
The entire technique is one division and one CSS transform. No libraries, no build tools, no complexity. It just works.