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();
Copied to clipboard!