13 CSS Neumorphism & Soft UI Designs

Nexus

A cool blue-gray light-neumorphic room control dashboard. Features a live arc-dial temperature control with +/− buttons, a 2×2 device card grid (each with inset toggle switches, active color-stripe headers, and individual accent colors), room selector pills, and a security status bar with pulsing indicator dots. Every device card is fully toggleable. Syne typeface for a clean modern edge. Best for smart-home apps, IoT dashboards, control panels.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<section class="nm-nex" aria-label="Smart home control panel">
  <div class="card">

  <div class="room-row" role="tablist">
    <button type="button" class="room-pill active" role="tab" aria-selected="true">Living Room</button>
    <button type="button" class="room-pill" role="tab" aria-selected="false">Bedroom</button>
    <button type="button" class="room-pill" role="tab" aria-selected="false">Kitchen</button>
    <button type="button" class="room-pill" role="tab" aria-selected="false">Office</button>
  </div>

  <div class="temp-section">
    <div class="dial-wrap" aria-hidden="true">
      <svg viewBox="0 0 90 90">
        <defs>
          <linearGradient id="nm-nex-tempgrad" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:#5b8def"/>
            <stop offset="100%" style="stop-color:#f0a855"/>
          </linearGradient>
        </defs>
        <circle class="track" cx="45" cy="45" r="35"/>
        <circle class="fill" cx="45" cy="45" r="35" id="nm-nex-dialfill"/>
      </svg>
      <div class="dial-center">
        <div class="dial-val" id="nm-nex-tempval">22</div>
        <div class="dial-unit">&deg;C</div>
      </div>
    </div>

    <div class="temp-info">
      <div class="temp-label">Climate Control</div>
      <div class="temp-sub">Humidity 48% &middot; Feels 23&deg;</div>
      <div class="temp-btns">
        <button type="button" class="adj-btn" data-nm-nex-adj="-1" aria-label="Decrease temperature">&minus;</button>
        <button type="button" class="adj-btn" data-nm-nex-adj="1" aria-label="Increase temperature">+</button>
      </div>
    </div>
  </div>

  <div class="devices-grid">
    <button type="button" class="device-card active" data-nm-nex-device="light" style="--device-color: #f0a855;">
      <span class="toggle" aria-hidden="true"></span>
      <span class="device-icon" aria-hidden="true">💡</span>
      <div class="device-name">Main Light</div>
      <div class="device-status">On &middot; 70%</div>
    </button>

    <button type="button" class="device-card active" data-nm-nex-device="air" style="--device-color: #5b8def;">
      <span class="toggle" aria-hidden="true"></span>
      <span class="device-icon" aria-hidden="true">❄️</span>
      <div class="device-name">Air Purifier</div>
      <div class="device-status">Running</div>
    </button>

    <button type="button" class="device-card" data-nm-nex-device="tv" style="--device-color: #5bb87e;">
      <span class="toggle" aria-hidden="true"></span>
      <span class="device-icon" aria-hidden="true">📺</span>
      <div class="device-name">Smart TV</div>
      <div class="device-status">Off</div>
    </button>

    <button type="button" class="device-card active" data-nm-nex-device="speaker" style="--device-color: #5bb87e;">
      <span class="toggle" aria-hidden="true"></span>
      <span class="device-icon" aria-hidden="true">🔊</span>
      <div class="device-name">Speaker</div>
      <div class="device-status">Playing</div>
    </button>
  </div>

  <div class="security-row">
    <div class="sec-left">
      <span class="sec-icon" aria-hidden="true">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
        </svg>
      </span>
      <div class="sec-text">
        <div class="sec-title">Security System</div>
        <div class="sec-sub">All Clear &middot; Armed</div>
      </div>
    </div>
    <div class="status-dots" aria-hidden="true">
      <span class="sdot green pulse"></span>
      <span class="sdot green"></span>
      <span class="sdot"></span>
    </div>
  </div>

  </div>
</section>
/* ─── 03 Nexus — cool blue-gray smart home control panel ─────────── */
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;500;700&family=Syne+Mono&display=swap');

.nm-nex {
  --nm-nex-bg: #dce4ee;
  --nm-nex-dark: #b8c4d2;
  --nm-nex-light: #ffffff;
  --nm-nex-text: #5a6880;
  --nm-nex-text-dim: #96a4b8;
  --nm-nex-on: #5b8def;
  --nm-nex-warm: #f0a855;
  --nm-nex-green: #5bb87e;
  --nm-nex-red: #e07070;
  --nm-nex-inset: #d4dce8;

  position: relative;
  width: 100%;
  min-height: 720px;
  background: var(--nm-nex-bg);
  font-family: 'Syne', system-ui, sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
  overflow: hidden;
  box-sizing: border-box;
}

.nm-nex *,
.nm-nex *::before,
.nm-nex *::after { box-sizing: border-box; }

/* The inner card holds the neumorphic shadow */
.nm-nex .card {
  position: relative;
  width: 100%;
  max-width: 440px;
  background: var(--nm-nex-bg);
  border-radius: 36px;
  padding: 36px 34px 34px;
  box-shadow:
    14px 14px 30px var(--nm-nex-dark),
    -12px -12px 26px var(--nm-nex-light);
}

/* Room pills */
.nm-nex .room-row {
  display: flex;
  gap: 8px;
  margin-bottom: 26px;
  overflow-x: auto;
  scrollbar-width: none;
}
.nm-nex .room-row::-webkit-scrollbar { display: none; }
.nm-nex .room-pill {
  flex-shrink: 0;
  padding: 7px 16px;
  border-radius: 20px;
  border: none;
  background: var(--nm-nex-bg);
  font-family: 'Syne', system-ui, sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: var(--nm-nex-text-dim);
  cursor: pointer;
  box-shadow:
    4px 4px 10px var(--nm-nex-dark),
    -3px -3px 8px var(--nm-nex-light);
  transition: all 0.2s;
}
.nm-nex .room-pill.active {
  background: var(--nm-nex-on);
  color: #fff;
  box-shadow:
    4px 4px 12px rgba(91, 141, 239, 0.35),
    -2px -2px 6px rgba(255, 255, 255, 0.5),
    inset 1px 1px 3px rgba(255, 255, 255, 0.2);
}

/* Temp dial */
.nm-nex .temp-section {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 22px;
  background: var(--nm-nex-bg);
  border-radius: 24px;
  padding: 18px;
  box-shadow:
    inset 5px 5px 12px var(--nm-nex-dark),
    inset -4px -4px 10px var(--nm-nex-light);
}
.nm-nex .dial-wrap {
  position: relative;
  width: 90px;
  height: 90px;
  flex-shrink: 0;
}
.nm-nex .dial-wrap svg {
  width: 100%;
  height: 100%;
  transform: rotate(-220deg);
}
.nm-nex .dial-wrap svg .track {
  fill: none;
  stroke: var(--nm-nex-dark);
  stroke-width: 5;
  stroke-linecap: round;
  opacity: 0.5;
}
.nm-nex .dial-wrap svg .fill {
  fill: none;
  stroke: url(#nm-nex-tempgrad);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 220;
  stroke-dashoffset: 55;
  transition: stroke-dashoffset 0.5s;
}
.nm-nex .dial-center {
  position: absolute;
  inset: 14px;
  border-radius: 50%;
  background: var(--nm-nex-bg);
  box-shadow:
    3px 3px 8px var(--nm-nex-dark),
    -2px -2px 6px var(--nm-nex-light);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.nm-nex .dial-val {
  font-family: 'Syne Mono', ui-monospace, monospace;
  font-size: 18px;
  color: var(--nm-nex-text);
  line-height: 1;
}
.nm-nex .dial-unit {
  font-size: 8px;
  color: var(--nm-nex-text-dim);
  letter-spacing: 1px;
}
.nm-nex .temp-info { flex: 1; }
.nm-nex .temp-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--nm-nex-text);
  margin-bottom: 4px;
  letter-spacing: 0.5px;
}
.nm-nex .temp-sub {
  font-size: 10px;
  color: var(--nm-nex-text-dim);
  margin-bottom: 14px;
}
.nm-nex .temp-btns { display: flex; gap: 8px; }
.nm-nex .adj-btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: none;
  background: var(--nm-nex-bg);
  cursor: pointer;
  font-size: 16px;
  color: var(--nm-nex-text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow:
    3px 3px 7px var(--nm-nex-dark),
    -2px -2px 5px var(--nm-nex-light);
  transition: all 0.15s;
  font-weight: 500;
  font-family: inherit;
}
.nm-nex .adj-btn:active {
  box-shadow:
    inset 2px 2px 5px var(--nm-nex-dark),
    inset -1px -1px 4px var(--nm-nex-light);
  color: var(--nm-nex-on);
}

/* Device grid */
.nm-nex .devices-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-bottom: 20px;
}
.nm-nex .device-card {
  background: var(--nm-nex-bg);
  border: 0;
  border-radius: 20px;
  padding: 18px 16px;
  box-shadow:
    6px 6px 14px var(--nm-nex-dark),
    -5px -5px 12px var(--nm-nex-light);
  cursor: pointer;
  transition: all 0.2s;
  position: relative;
  overflow: hidden;
  text-align: left;
  font-family: inherit;
}
.nm-nex .device-card.active::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  border-radius: 20px 20px 0 0;
  background: var(--device-color, var(--nm-nex-on));
}
.nm-nex .device-icon {
  width: 36px;
  height: 36px;
  border-radius: 12px;
  background: var(--nm-nex-bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 12px;
  font-size: 16px;
  box-shadow:
    3px 3px 8px var(--nm-nex-dark),
    -2px -2px 6px var(--nm-nex-light);
  transition: all 0.2s;
}
.nm-nex .device-card.active .device-icon {
  background: var(--device-color, var(--nm-nex-on));
  box-shadow:
    2px 2px 6px rgba(0, 0, 0, 0.15),
    inset 1px 1px 3px rgba(255, 255, 255, 0.25);
  color: #fff;
}
.nm-nex .device-name {
  font-size: 11px;
  font-weight: 700;
  color: var(--nm-nex-text);
  margin-bottom: 2px;
  letter-spacing: 0.3px;
}
.nm-nex .device-status {
  font-size: 9px;
  color: var(--nm-nex-text-dim);
  letter-spacing: 0.5px;
  font-family: 'Syne Mono', ui-monospace, monospace;
}
.nm-nex .device-card.active .device-status { color: var(--device-color, var(--nm-nex-on)); }

/* Toggle switch */
.nm-nex .toggle {
  position: absolute;
  top: 16px;
  right: 14px;
  width: 28px;
  height: 16px;
  border-radius: 8px;
  background: var(--nm-nex-bg);
  box-shadow:
    inset 2px 2px 5px var(--nm-nex-dark),
    inset -1px -1px 4px var(--nm-nex-light);
  cursor: pointer;
  transition: all 0.2s;
}
.nm-nex .toggle::after {
  content: '';
  position: absolute;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--nm-nex-text-dim);
  top: 2.5px;
  left: 3px;
  transition: all 0.25s;
  box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
.nm-nex .device-card.active .toggle {
  background: var(--device-color, var(--nm-nex-on));
  box-shadow:
    inset 1px 1px 3px rgba(0, 0, 0, 0.25),
    inset -1px -1px 2px rgba(255, 255, 255, 0.2);
}
.nm-nex .device-card.active .toggle::after {
  left: calc(100% - 14px);
  background: #fff;
}

/* Security row */
.nm-nex .security-row {
  background: var(--nm-nex-bg);
  border-radius: 18px;
  padding: 16px 18px;
  box-shadow:
    inset 4px 4px 10px var(--nm-nex-dark),
    inset -3px -3px 8px var(--nm-nex-light);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.nm-nex .sec-left { display: flex; align-items: center; gap: 12px; }
.nm-nex .sec-icon {
  width: 36px;
  height: 36px;
  border-radius: 12px;
  background: var(--nm-nex-bg);
  box-shadow:
    3px 3px 7px var(--nm-nex-dark),
    -2px -2px 5px var(--nm-nex-light);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  color: var(--nm-nex-green);
}
.nm-nex .sec-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--nm-nex-text);
  margin-bottom: 2px;
}
.nm-nex .sec-sub {
  font-size: 9px;
  color: var(--nm-nex-green);
  font-family: 'Syne Mono', ui-monospace, monospace;
  letter-spacing: 0.5px;
}
.nm-nex .status-dots { display: flex; gap: 5px; }
.nm-nex .sdot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--nm-nex-bg);
  box-shadow:
    1px 1px 3px var(--nm-nex-dark),
    -1px -1px 2px var(--nm-nex-light);
}
.nm-nex .sdot.green {
  background: var(--nm-nex-green);
  box-shadow: 0 0 6px rgba(91, 184, 126, 0.5);
}
.nm-nex .sdot.pulse { animation: nm-nex-dot 2s ease-in-out infinite; }
@keyframes nm-nex-dot {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

@media (prefers-reduced-motion: reduce) {
  .nm-nex .sdot.pulse { animation: none; }
  .nm-nex .dial-wrap svg .fill { transition: none; }
}
(() => {
  const root = document.querySelector('.nm-nex');
  if (!root) return;
  const statusMap = {
    light:   { on: 'On · 70%', off: 'Off' },
    air:     { on: 'Running',  off: 'Off' },
    tv:      { on: 'On',       off: 'Off' },
    speaker: { on: 'Playing',  off: 'Off' },
  };
  root.querySelectorAll('.device-card').forEach(card => {
    card.addEventListener('click', () => {
      card.classList.toggle('active');
      const key = card.dataset.nmNexDevice;
      const status = card.querySelector('.device-status');
      const labels = statusMap[key];
      if (status && labels) {
        status.textContent = card.classList.contains('active') ? labels.on : labels.off;
      }
    });
  });

  let temp = 22;
  const tempVal = root.querySelector('#nm-nex-tempval');
  const dialFill = root.querySelector('#nm-nex-dialfill');
  root.querySelectorAll('[data-nm-nex-adj]').forEach(btn => {
    btn.addEventListener('click', () => {
      const delta = parseInt(btn.dataset.nmNexAdj, 10) || 0;
      temp = Math.max(16, Math.min(30, temp + delta));
      if (tempVal) tempVal.textContent = temp;
      if (dialFill) {
        const pct = (temp - 16) / 14;
        dialFill.style.strokeDashoffset = String(220 - pct * 220);
      }
    });
  });

  root.querySelectorAll('.room-pill').forEach(p => {
    p.addEventListener('click', () => {
      root.querySelectorAll('.room-pill').forEach(o => {
        o.classList.remove('active');
        o.setAttribute('aria-selected', 'false');
      });
      p.classList.add('active');
      p.setAttribute('aria-selected', 'true');
    });
  });
})();

Search CodeFronts

Loading…