Developer

CSS Minifier

Minify CSS to shrink file size by stripping whitespace, comments, and unnecessary semicolons — or beautify with clean indentation and one property per line.

Original
Output
Saved
Rules
Output
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

function formatBytes(n) {
    if (n < 1024) return n + ' B';
    return (n / 1024).toFixed(1) + ' KB';
  }

  function updateStats(orig, out) {
    const ob = new TextEncoder().encode(orig).length;
    const outb = out !== null ? new TextEncoder().encode(out).length : null;
    document.getElementById('statOrig').textContent = formatBytes(ob);
    document.getElementById('statOut').textContent = outb !== null ? formatBytes(outb) : '—';
    if (outb !== null && ob > 0) {
      const pct = Math.round((1 - outb / ob) * 100);
      document.getElementById('statSaved').textContent = (pct > 0 ? '-' : '+') + Math.abs(pct) + '%';
    } else {
      document.getElementById('statSaved').textContent = '—';
    }
    // Count rules (opening braces outside strings/comments)
    const rules = (orig.match(/\{/g) || []).length;
    document.getElementById('statRules').textContent = rules || '—';
  }

  function onInput() {
    const input = document.getElementById('cssInput').value;
    if (input.trim()) {
      updateStats(input, null);
    }
  }

  function stripComments(css) {
    return css.replace(/\/\*[\s\S]*?\*\//g, '');
  }

  function minifyCSS() {
    const input = document.getElementById('cssInput').value;
    if (!input.trim()) return;

    let css = stripComments(input);
    css = css
      .replace(/\s*\{\s*/g, '{')
      .replace(/\s*\}\s*/g, '}')
      .replace(/\s*:\s*/g, ':')
      .replace(/\s*;\s*/g, ';')
      .replace(/\s*,\s*/g, ',')
      .replace(/;+\}/g, '}')          // remove last semicolon before }
      .replace(/\s+/g, ' ')
      .trim();

    document.getElementById('cssOutput').innerHTML = escapeHtml(css);
    updateStats(input, css);
  }

  function beautifyCSS() {
    const input = document.getElementById('cssInput').value;
    if (!input.trim()) return;

    // First minify to normalize, then reformat
    let css = stripComments(input)
      .replace(/\s+/g, ' ')
      .trim();

    let result = '';
    let indent = 0;
    let i = 0;

    while (i < css.length) {
      const c = css[i];

      if (c === '{') {
        result = result.trimEnd() + ' {\n';
        indent++;
        i++;
        // Skip whitespace after {
        while (i < css.length && css[i] === ' ') i++;
        continue;
      }

      if (c === '}') {
        indent = Math.max(0, indent - 1);
        result = result.trimEnd() + '\n' + '  '.repeat(indent) + '}\n\n';
        i++;
        while (i < css.length && css[i] === ' ') i++;
        continue;
      }

      if (c === ';') {
        result = result.trimEnd() + ';\n';
        i++;
        // Skip space after ;
        while (i < css.length && css[i] === ' ') i++;
        if (i < css.length && css[i] !== '}') {
          result += '  '.repeat(indent);
        }
        continue;
      }

      if (c === ' ' && result.endsWith('\n')) {
        i++;
        continue;
      }

      // Start of a new line inside a rule block
      if (indent > 0 && (result.endsWith('\n') || result.endsWith('{\n'))) {
        result += '  '.repeat(indent);
      }

      result += c;
      i++;
    }

    result = result.replace(/\n{3,}/g, '\n\n').trim();
    document.getElementById('cssOutput').innerHTML = highlightCSS(result);
    updateStats(input, result);
  }

  function highlightCSS(css) {
    const esc = s => s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
    // Simple line-by-line highlight
    return css.split('\n').map(line => {
      const trimmed = line.trim();
      // Comment
      if (trimmed.startsWith('/*')) return '<span class="cc">' + esc(line) + '</span>';
      // Selector line (ends with { or is just })
      if (trimmed.endsWith('{') || trimmed === '}') return '<span class="cs">' + esc(line) + '</span>';
      // Property: value;
      const m = line.match(/^(\s*)([\w-]+)(\s*:\s*)(.+)$/);
      if (m) {
        return esc(m[1]) + '<span class="cp">' + esc(m[2]) + '</span>' + esc(m[3]) + '<span class="cv">' + esc(m[4]) + '</span>';
      }
      return esc(line);
    }).join('\n');
  }

  function escapeHtml(s) {
    return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }

  function copyOutput(btn) {
    const text = document.getElementById('cssOutput').textContent;
    if (!text) return;
    navigator.clipboard.writeText(text).catch(() => {});
    btn.textContent = 'copied!';
    btn.classList.add('copied');
    setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
  }

  function clearAll() {
    document.getElementById('cssInput').value = '';
    document.getElementById('cssOutput').innerHTML = '';
    document.getElementById('statOrig').textContent = '—';
    document.getElementById('statOut').textContent = '—';
    document.getElementById('statSaved').textContent = '—';
    document.getElementById('statRules').textContent = '—';
  }

  document.getElementById('cssInput').addEventListener('keydown', function(e) {
    if (e.key === 'Tab') {
      e.preventDefault();
      const s = this.selectionStart, end = this.selectionEnd;
      this.value = this.value.substring(0, s) + '  ' + this.value.substring(end);
      this.selectionStart = this.selectionEnd = s + 2;
    }
  });

  // Sample CSS
  document.getElementById('cssInput').value = `/* Main layout */
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  max-width: 1200px;
  margin: 0 auto;
  padding: 24px 16px;
}

/* Navigation */
.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(10,10,10,0.9);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid #1e1e1e;
  height: 52px;
}

.nav a {
  color: #e8e0d5;
  text-decoration: none;
  font-size: 14px;
  transition: opacity 0.2s ease;
}

.nav a:hover { opacity: 0.8; }`;
  onInput();