In CSS, what is the difference between setting `perspective` on a parent element and using the `perspective()` function inside a child's own `transform`?
answer
- one scene versus many little scenes
- viewer distance, not element depth
- shared vanishing point on the parent
- nested 3D needs preserve-3d
- overflow and opacity force flattening
basics
~20 sThe perspective property on a parent gives all its children one shared vanishing point, so they read as objects in a single scene. The perspective() function inside a child's own transform gives that child its own vanishing point, projected from its own origin.
solid answer
~40 sBoth establish how strongly 3D transforms are foreshortened, but they differ in scope. `perspective: 800px` on a parent creates one 3D rendering context for its children: every child is projected from the same viewer position, located at the parent's `perspective-origin` (default `50% 50%`), so a row of rotated cards looks like one scene viewed from one place. Writing `transform: perspective(800px) rotateY(30deg)` on a child applies the projection to that element alone, computed from its own origin, so each card looks independently projected and a row of them all lean identically. Smaller values mean a nearer viewer and a more extreme distortion. The parent form is normally what you want for a group; the function is convenient for a single isolated element. Either way, nested 3D needs `transform-style: preserve-3d` on the intermediate elements.
go deeper
Know that some perspective value is required before a 3D rotation looks like a rotation rather than a squash, and that it can live on a parent or in the element's own transform.
Explain the scope difference precisely — one shared vanishing point for all children versus a private one per element — and name perspective-origin and transform-style: preserve-3d.
Show diagnostic instinct: when a preserve-3d effect breaks, check for grouping properties such as overflow, opacity below 1 or a filter that force flattening, and know that a large transformed surface is not cheap.
Decide how far a design system goes into 3D at all — what motion it buys, the accessibility and rendering cost it carries, and whether the effect degrades acceptably where it is not supported or not wanted.
## Perspective is the viewer's distance A 3D transform such as `rotateY(45deg)` or `translateZ(50px)` does nothing convincing on its own: without a perspective value the projection is orthographic, so a rotated element just looks squashed rather than turned away from you. Perspective supplies the missing piece — how far the notional viewer is from the z = 0 plane. Smaller values put the viewer closer and exaggerate the effect; larger values flatten it toward orthographic. The value must be a positive length; `0` and negative lengths are invalid. ## Two places to declare it **On the parent, as a property:** ```css .scene { perspective: 800px; perspective-origin: 50% 50%; /* the default */ } .scene > .card { transform: rotateY(30deg); } ``` The parent establishes a shared 3D rendering context. Every child is projected from the *same* viewer position, sitting at `perspective-origin` relative to the parent's box. Cards on the left of the scene therefore appear to turn away differently from cards on the right, exactly as real objects on a table do. This is what you want whenever several elements should read as one scene. **On the child, as a transform function:** ```css .card { transform: perspective(800px) rotateY(30deg); } ``` Here the projection applies only to this element, computed against its own `transform-origin`. Ten cards written this way all look identically projected — each is its own little scene — which reads as flat and repetitive for a group, but is perfectly fine for one isolated element where adding a wrapper would be noise. Because `perspective()` is a transform function, it obeys the ordering rule of transform lists and is conventionally written first; putting it after the rotation changes the result. ## `transform-style` and the flattening trap By default, a transformed element flattens its descendants into its own plane: `transform-style: flat` is the initial value. That is why the canonical card flip fails on the first attempt — the container rotates, but the two faces inside it collapse onto one plane instead of turning away from each other. ```css .flip-scene { perspective: 1000px; } .flip-inner { position: relative; transform-style: preserve-3d; /* the line people forget */ transition: transform 500ms; } .flip-scene:hover .flip-inner { transform: rotateY(180deg); } .face { position: absolute; inset: 0; backface-visibility: hidden; /* hide the mirrored reverse */ } .face--back { transform: rotateY(180deg); } ``` The crueller half of this is that certain declarations force flattening **regardless** of `transform-style: preserve-3d`. Elements that group their content — `overflow` other than `visible`, `opacity` less than `1`, a `filter`, a `clip-path`, a `mask` — must be flattened. So a card that works perfectly gains `overflow: hidden` to clip an image, or fades in with `opacity: 0.99` during a transition, and silently loses its 3D. When a preserve-3d effect "stops working for no reason", this list is the first thing to check. ## `backface-visibility` Rotating an element past 90° shows its mirrored reverse. `backface-visibility: hidden` makes that reverse invisible, which is how a flip shows two different faces: both faces sit stacked in the same place, the back one pre-rotated 180°, and whichever is facing away is hidden. ## Choosing between them - A group that must share a scene — a fanned deck, a carousel, a set of tiles tilting toward a common vanishing point — needs the **property on a shared ancestor**. - A single element with a self-contained effect, or a component that cannot rely on its parent's markup, is fine with the **function**. - The two combine rather than conflict: a child with its own `perspective()` inside a parent that also sets `perspective` is projected through both, which is almost never what anyone intends and is a good thing to spot in review. A good short answer names the scope difference first (shared viewer versus per-element viewer), then mentions `perspective-origin` for moving the vanishing point, and `transform-style: preserve-3d` for anything nested.
- A card flip rotates the container but the two faces never turn away from each other. What is missing?`transform-style: preserve-3d` on the rotating container. Without it the container flattens its children into its own plane, so both faces stay coplanar and the back never swings behind. Add that, give each face `backface-visibility: hidden`, and pre-rotate the back face by 180deg.
- Why might a preserve-3d effect stop working after someone adds `overflow: hidden` to the container?Because certain properties force flattening no matter what transform-style says — overflow other than visible, opacity below 1, filter, clip-path and mask all group the element's content and flatten it. The fix is to move the clipping to a different element in the chain, so the one carrying preserve-3d has none of those applied.
- What does `perspective-origin` change?It moves the notional viewer position relative to the element that sets `perspective`, defaulting to `50% 50%` — the centre. Shifting it to `perspective-origin: 0 50%` makes the scene look as though you are standing to the left, so children lean and foreshorten as if seen from that angle. It has no effect where no perspective is set.
The parent property is one camera photographing a table of objects; the function is a separate camera bolted to each object.
saying these in an interview costs you the question
- Says perspective sets how far the element moves in 3D
- Thinks the function and the parent property are interchangeable
- Forgets preserve-3d and expects nested 3D to work
- Claims a larger perspective value gives a stronger effect
- Believes backface-visibility creates the 3D space