CSS & Design Tools
CSS SHADOW STUDIO
Build smooth, realistic multi-layered CSS box shadows with live elevation presets, inset support, and instant code copy.
Elevation Presets
CARD PREVIEW
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
let layers = [
{ x: 0, y: 10, blur: 25, spread: -3, color: '#000000', opacity: 50, inset: false },
{ x: 0, y: 4, blur: 6, spread: -2, color: '#000000', opacity: 30, inset: false }
];
const layersContainer = document.getElementById('layers-container');
const previewBox = document.getElementById('preview-box');
const cssOutput = document.getElementById('css-output');
const toast = document.getElementById('toast');
function showToast(msg) {
toast.textContent = msg;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2000);
}
function renderLayerControls() {
layersContainer.innerHTML = '';
layers.forEach((l, idx) => {
const card = document.createElement('div');
card.className = 'layer-card';
card.innerHTML = `
<div class="layer-header">
<span class="layer-title">Layer #${idx + 1}</span>
${layers.length > 1 ? `<button class="btn-del-layer" onclick="deleteLayer(${idx})">โ</button>` : ''}
</div>
<div class="ctrl-group">
<div class="ctrl-header"><span class="ctrl-label">X Offset</span><span class="ctrl-val">${l.x}px</span></div>
<input type="range" min="-50" max="50" value="${l.x}" oninput="updateLayer(${idx}, 'x', this.value)">
</div>
<div class="ctrl-group">
<div class="ctrl-header"><span class="ctrl-label">Y Offset</span><span class="ctrl-val">${l.y}px</span></div>
<input type="range" min="-50" max="50" value="${l.y}" oninput="updateLayer(${idx}, 'y', this.value)">
</div>
<div class="ctrl-group">
<div class="ctrl-header"><span class="ctrl-label">Blur Radius</span><span class="ctrl-val">${l.blur}px</span></div>
<input type="range" min="0" max="100" value="${l.blur}" oninput="updateLayer(${idx}, 'blur', this.value)">
</div>
<div class="ctrl-group">
<div class="ctrl-header"><span class="ctrl-label">Spread Radius</span><span class="ctrl-val">${l.spread}px</span></div>
<input type="range" min="-30" max="30" value="${l.spread}" oninput="updateLayer(${idx}, 'spread', this.value)">
</div>
`;
layersContainer.appendChild(card);
});
updateShadowCss();
}
window.updateLayer = function(idx, key, val) {
layers[idx][key] = parseInt(val);
renderLayerControls();
};
window.deleteLayer = function(idx) {
layers.splice(idx, 1);
renderLayerControls();
};
function updateShadowCss() {
const parts = layers.map(l => {
const hex = l.color;
const op = (l.opacity / 100).toFixed(2);
const rgba = `rgba(0, 0, 0, ${op})`;
return `${l.inset ? 'inset ' : ''}${l.x}px ${l.y}px ${l.blur}px ${l.spread}px ${rgba}`;
});
const shadowVal = parts.join(',\n ');
const cssStr = `box-shadow: ${shadowVal};`;
previewBox.style.boxShadow = parts.join(', ');
cssOutput.textContent = cssStr;
}
document.getElementById('btn-add-layer').addEventListener('click', () => {
layers.push({ x: 0, y: 15, blur: 30, spread: 0, color: '#000000', opacity: 40, inset: false });
renderLayerControls();
});
document.querySelectorAll('.btn-preset').forEach(btn => {
btn.addEventListener('click', () => {
const p = btn.dataset.preset;
if (p === 'soft') {
layers = [
{ x: 0, y: 20, blur: 30, spread: -5, color: '#000000', opacity: 40, inset: false },
{ x: 0, y: 8, blur: 10, spread: -3, color: '#000000', opacity: 20, inset: false }
];
} else if (p === 'retro') {
layers = [
{ x: 8, y: 8, blur: 0, spread: 0, color: '#000000', opacity: 100, inset: false }
];
} else if (p === 'glow') {
layers = [
{ x: 0, y: 0, blur: 25, spread: 5, color: '#000000', opacity: 80, inset: false }
];
} else if (p === 'dark') {
layers = [
{ x: 0, y: 30, blur: 60, spread: -10, color: '#000000', opacity: 70, inset: false }
];
}
renderLayerControls();
});
});
document.getElementById('btn-copy-css').addEventListener('click', () => {
navigator.clipboard.writeText(cssOutput.textContent);
showToast('box-shadow CSS copied!');
});
renderLayerControls();