HTML CSS JS Playground & CSS Generators (Shadow, Gradient)Beta
Live HTML/CSS/JS editor plus CSS value generators
This tool is a beta version still being stabilized. Please let us know if a result looks off.
Live HTML/CSS/JS editor plus CSS value generators
Console0
Auto-saved in your browser. Your code is not sent to a server — only when it is too long for a link, and only with your consent, is it stored on the share server.
Your code is too long for a link. Store it on the share server and share a short code instead?
It is deleted automatically if nobody opens it for the period you pick. Every time someone opens the code the clock resets. Choose “No” and nothing is uploaded — you get the long link as is.
Your share code is ready
Tell someone the code (More → Open a share code), or just send the link below.
Loading…
Working…
0.0
It hasn't frozen — the result will appear when the calculation finishes.
About HTML CSS JS Playground & CSS Generators (Shadow, Gradient)
Whatever you type into the HTML, CSS and JS panes is stitched into a single HTML document about 600 ms after you stop typing, and that document is loaded into the preview iframe through its srcdoc attribute. CSS goes into a style tag in the head, your HTML becomes the body, and your JavaScript is placed in a script tag at the very end of the body. By the time your script runs, every element above it already exists, which is why document.getElementById works here without wrapping anything in a DOMContentLoaded listener.
The page also carries four CSS generators that used to be separate tools: box-shadow, border-radius, gradients and a cubic-bezier easing editor. They sit in tabs above the editor so you can build a value, paste it into the CSS pane and see it on a real element straight away. The sections below cover how the preview is isolated from the rest of the site, and how the browser actually computes the properties each generator produces.
How the preview sandbox works
The preview iframe is declared with sandbox=“allow-scripts allow-modals allow-forms allow-popups”. The token that is deliberately missing is allow-same-origin. Without it, the framed document gets an opaque origin that matches no real site, so your practice code cannot read this site's cookies or storage. MDN spells out why the two tokens should not be combined for same-origin content: with both allow-scripts and allow-same-origin, the framed page can simply remove its own sandbox attribute, which leaves you no safer than having no sandbox at all.
The practical consequence is that localStorage, sessionStorage and document.cookie will throw or silently store nothing inside the preview. That is the isolation doing its job, not a bug in your code. If you are testing persistence, use “Save as HTML file” and open the download directly. Dialogs such as alert() and confirm() do work because of allow-modals, and window.open() or target=“_blank” links open thanks to allow-popups. Form submission is allowed by allow-forms.
The console panel is fed by a tiny bridge script injected at the top of the head, before any of your code. It wraps console.log, info, warn and error, listens for uncaught errors and unhandled promise rejections, and forwards each one to the parent page with postMessage. The parent checks that the message came from the current iframe and carries the latest run number, so a late log from a previous run can't leak into the new one. Error line numbers are recalculated to match the JS pane rather than the merged document.
Why a literal </script> breaks inline scripts
The HTML parser does not understand JavaScript. Inside a script element it scans raw text until it sees </script, and closes the element right there, even if that sequence sits inside a JS string. So const s = “</script>” in an inline script cuts the script in half. When the playground assembles the document it rewrites </script in the JS pane to <\/script and </style in the CSS pane to <\/style. The backslash is harmless to JavaScript and invisible to the parser. You need the same escape whenever you inline a script into a hand-written HTML file.
box-shadow: layers stack top-down
box-shadow accepts a comma-separated list, and the first shadow in the list is painted on top. Each layer is offset-x, offset-y, blur radius, spread radius, a color and an optional inset keyword. Blur cannot be negative; a negative spread shrinks the shadow before it is blurred. Shadows never change the element's box size, so a huge shadow won't push its neighbours around, but it can be clipped by an ancestor with overflow: hidden.
Realistic depth usually needs two layers. A short, dark shadow imitates the contact area right under the object, while a longer, fainter one imitates ambient light spreading out. The elevation presets follow that recipe. The neumorphism presets pair a grey shadow on one side with a white one on the other so the element appears to rise out of the surface; it only reads correctly when the background color sits between those two shadow colors. The generator always writes all four lengths, even zeros, and always writes colors as rgba() so the output is predictable to paste and edit.
border-radius: what happens when the radii are too big
When the radii touching one side add up to more than that side's length, CSS Backgrounds Level 3 scales every radius by the same factor f = min(side length ÷ sum of the two radii on that side). Take a 200×100 px box with border-radius: 100px. The left and right sides are 100 px tall but carry 200 px of radius, so f = 0.5 and every corner drops to 50 px. The result is a pill. With border-radius: 50%, the horizontal radius is half the width and the vertical radius half the height, so the same box becomes an ellipse. That is the whole reason designers write 9999px for pill buttons instead of 50%.
A slash separates horizontal radii from vertical ones. The blob preset randomizes all eight values, which is how you get organic shapes. When values repeat, the generator collapses them with the same one-to-four value shorthand rules used by margin and padding.
Which color space a gradient mixes in
A linear gradient from #ff0000 to #00ff00 hits #808000 at its midpoint, a muddy olive, because the channels are averaged in gamma-encoded sRGB. The gradient generator uses the same arithmetic when you click to insert a stop between two others. CSS Color Level 4 keeps sRGB as the default when every color uses legacy syntax such as hex or rgb(), and CSS Images Level 4 adds an explicit interpolation hint, for example linear-gradient(in oklch, …) or linear-gradient(in oklab to right, …). If the middle of a gradient between two distant hues looks grey, try oklab or oklch in browsers that support it.
When transparency is involved, browsers interpolate in premultiplied form: each color is multiplied by its alpha before mixing. A fade from solid red to rgba(0,0,0,0) therefore passes through semi-transparent red rather than a dark reddish-brown, even though the transparent end is technically black. The gradient generator mixes the same way when you click to insert a stop, so halfway between red and transparent it inserts rgba(255, 0, 0, 0.5).
cubic-bezier: reading the four numbers
cubic-bezier(x1, y1, x2, y2) gives the two control points of a curve fixed at (0, 0) and (1, 1). The x axis is time and the y axis is progress. Per CSS Easing Level 1, both x values must lie in [0, 1] or the whole declaration is invalid; y values can be anything. A y above 1 overshoots the target and settles back, and a y below 0 pulls back before starting. While you drag, the editor keeps the handles inside the visible graph (y from -0.8 to 1.9) so they don't fly off the canvas; larger values typed into the number fields or pasted in are used as-is, without clamping.
- ease = cubic-bezier(0.25, 0.1, 0.25, 1), the default for transition-timing-function
- ease-in = cubic-bezier(0.42, 0, 1, 1)
- ease-out = cubic-bezier(0, 0, 0.58, 1)
- ease-in-out = cubic-bezier(0.42, 0, 0.58, 1)
- linear = cubic-bezier(0, 0, 1, 1)
Worked examples
ease covers 80% of the distance in half the time
- Input
cubic-bezier(0.25, 0.1, 0.25, 1) at 50% elapsed time- Output
about 80.2% progress
Computed with the editor's own solver. At the same moment ease-in-out is at exactly 50%, and at 25% of the time it has only reached 12.9%. Because ease front-loads the motion and then decelerates for a long time, it suits quick hover effects but can make long slides feel like they drag at the end.
A bouncy button with ease-out-back
- Input
cubic-bezier(0.34, 1.56, 0.64, 1)- Output
peak progress about 109.8% (108.7% at half time)
y1 = 1.56 is above 1, so the animation goes roughly 10% past its target before settling. Paired with transform: scale(0.92) on :active, the button puffs up slightly when released. The playground's card template uses exactly this curve.
What the elevation-2 preset actually outputs
- Input
Preset: elevation-2- Output
0px 2px 4px 0px rgba(0, 0, 0, 0.24), 0px 3px 6px 1px rgba(0, 0, 0, 0.14)
The first line is painted on top: a tight, darker contact shadow. The second spreads 1 px wider and is fainter. Delete the first line and the card looks like it floats with a fuzzy edge; delete the second and it looks pasted on.
Pill versus ellipse on a 200×100 box
- Input
border-radius: 100px vs border-radius: 50%- Output
100px → every corner scaled to 50px (pill); 50% → 100px × 50px elliptical corners (oval)
The scale factor is min(200/200, 100/200, 200/200, 100/200) = 0.5.
How to use it
- Pick the editor or one of the CSS generator tabs.
- Edit code; the preview and console refresh shortly after you pause. Copy generator output into the CSS pane.
- Share via link or download the result as a standalone HTML file.
Frequently asked questions
Why does my localStorage code fail in the preview?
The iframe is sandboxed without allow-same-origin, so it runs with an opaque origin that has no storage. Download the HTML file and open it directly to test persistence.
Where does a share link keep my code?
The code is compressed with deflate, encoded as base64url and placed after the # in the URL. Browsers don't send the fragment to the server. If the result is too long for a link, it is stored on the share server and you get an 8-character code whose first letter is the retention period: H for one hour, D for one day, Y for one year.
Can cubic-bezier take an x value like 1.2?
No. The spec requires both x values to be between 0 and 1, and browsers drop the declaration otherwise. The editor flags this input as an error. The y values have no limit.
Why does alert() work but window.open() sometimes doesn't?
Both are permitted by the sandbox tokens, but pop-ups can still be blocked by the browser's own pop-up blocker if they aren't triggered by a click.
My gradient looks dull in the middle.
Hex and rgba colors interpolate in sRGB by default. Add in oklch right after linear-gradient( to keep saturation in browsers that support interpolation hints.
Sources
- MDN — <iframe> sandbox attribute
- W3C CSS Easing Functions Level 1
- W3C CSS Backgrounds and Borders Level 3
- MDN — box-shadow
- W3C CSS Images Level 4
- W3C CSS Color Level 4
Last reviewed: