Pure CSSMIT licensed

Rotate3d and the Transform Matrix

rotate3d(x, y, z, angle) rotates around an arbitrary axis vector, which rotateX, rotateY and rotateZ cannot express between them. Four wireframe plates turn around four different vectors with the vector drawn on each, and the readout names the fact that all of it compiles down to one matrix3d().

Live Demo
Try it

Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time

Untitled UI · Affiliate link

Get Untitled UI Pro

The code

<section class="d3-11" aria-labelledby="d3-11-title">
  <h2 class="d3-11__sr" id="d3-11-title">Rotate3d and the transform matrix</h2>

  <div class="d3-11__theme">
    <input class="d3-11__theme-input" type="checkbox" id="d3-11-theme">
    <label class="d3-11__theme-label" for="d3-11-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
      <span class="d3-11__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="d3-11__stage">
    <div class="d3-11__viewport">
      <div class="d3-11__panel">
        <span class="d3-11__kicker">Season pass &middot; 2027</span>
        <strong class="d3-11__title">Rotating around an arbitrary axis</strong>
        <p class="d3-11__copy">Three separate rotations run one after another. <code>rotate3d(x, y, z, angle)</code> runs once, around a vector you choose &mdash; so the badge tumbles on its true diagonal instead of stepping through three axes.</p>
        <dl class="d3-11__read">
          <div><dt>axis</dt><dd>1, 1, 0.35</dd></div>
          <div><dt>angle</dt><dd>360deg</dd></div>
          <div><dt>matrix</dt><dd>4 &times; 4</dd></div>
        </dl>
        <ul class="d3-11__legend">
          <li><span class="d3-11__key d3-11__key--x"></span>X 1.0</li>
          <li><span class="d3-11__key d3-11__key--y"></span>Y 1.0</li>
          <li><span class="d3-11__key d3-11__key--z"></span>Z 0.35</li>
        </ul>
      </div>

      <div class="d3-11__scene">
        <div class="d3-11__rig">
          <div class="d3-11__badge">
            <span class="d3-11__holo" aria-hidden="true"></span>
            <span class="d3-11__mark">VX</span>
            <span class="d3-11__name">Vector Pass</span>
            <span class="d3-11__serial">No. 0117 / 5000</span>
          </div>
          <span class="d3-11__vector" aria-hidden="true"></span>
        </div>
      </div>
    </div>
  </div>
</section>
.d3-11 {
  --d3-11-bg: #17151c;
  --d3-11-surf: #201d28;
  --d3-11-line: #35303f;
  --d3-11-ink: #f2eff7;
  --d3-11-mut: #a49cb4;
  --d3-11-acc: #a26bff;
  --d3-11-acc: oklch(70% 0.19 296);
  --d3-11-acc2: #4de0d4;
  --d3-11-acc2: oklch(84% 0.13 186);
  --d3-11-spin: 14s;
  --d3-11-font-display: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  width: 100%;
  min-height: 100vh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: radial-gradient(48rem 26rem at 84% -8%, color-mix(in oklab, var(--d3-11-acc) 26%, transparent), transparent 62%), radial-gradient(40rem 22rem at -4% 100%, color-mix(in oklab, var(--d3-11-acc2) 18%, transparent), transparent 60%), var(--d3-11-bg);
  color: var(--d3-11-ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.5;
}

.d3-11 *,
.d3-11 *::before,
.d3-11 *::after {
  box-sizing: border-box;
}

.d3-11__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.d3-11:has(.d3-11__theme-input:checked) {
  --d3-11-bg: #f4f2f8;
  --d3-11-surf: #ffffff;
  --d3-11-line: #ded8e8;
  --d3-11-ink: #15121b;
  --d3-11-mut: #625a70;
  --d3-11-acc: #6a29d6;
  --d3-11-acc: oklch(48% 0.22 296);
  --d3-11-acc2: #007f77;
  --d3-11-acc2: oklch(52% 0.1 186);
}

@media (prefers-color-scheme: light) {
  .d3-11:has(.d3-11__theme-input:checked) {
    --d3-11-bg: #f4f2f8;
    --d3-11-surf: #ffffff;
    --d3-11-line: #ded8e8;
    --d3-11-ink: #15121b;
    --d3-11-mut: #625a70;
    --d3-11-acc: #6a29d6;
    --d3-11-acc: oklch(48% 0.22 296);
    --d3-11-acc2: #007f77;
    --d3-11-acc2: oklch(52% 0.1 186);
  }
}

@media (prefers-color-scheme: dark) {
  .d3-11:has(.d3-11__theme-input:checked) {
    --d3-11-bg: #f4f2f8;
    --d3-11-surf: #ffffff;
    --d3-11-line: #ded8e8;
    --d3-11-ink: #15121b;
    --d3-11-mut: #625a70;
    --d3-11-acc: #6a29d6;
    --d3-11-acc: oklch(48% 0.22 296);
    --d3-11-acc2: #007f77;
    --d3-11-acc2: oklch(52% 0.1 186);
  }
}

.d3-11__theme {
  position: absolute;
  inset-block-start: clamp(0.75rem, 3vw, 1.5rem);
  inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
  z-index: 20;
}

.d3-11__theme-input {
  position: absolute;
  opacity: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  cursor: pointer;
}

.d3-11__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 1px solid var(--d3-11-line);
  border-radius: 14px;
  background: var(--d3-11-surf);
  color: var(--d3-11-ink);
  cursor: pointer;
}

.d3-11__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

.d3-11__theme-input:focus-visible + .d3-11__theme-label {
  outline: 2px solid var(--d3-11-acc);
  outline-offset: 3px;
}

.d3-11__stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  min-block-size: 100vh;
  padding: clamp(3.5rem, 9vw, 5rem) clamp(0.75rem, 4vw, 2.5rem);
}

.d3-11__viewport {
  inline-size: 100%;
  max-inline-size: 62rem;
  overflow: hidden;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  align-items: center;
  gap: clamp(1rem, 3.5vw, 2rem);
  padding: clamp(1rem, 3.5vw, 2rem);
  border: 1px solid var(--d3-11-line);
  border-radius: 26px;
  background: color-mix(in oklab, var(--d3-11-surf) 76%, transparent);
  box-shadow: 0 40px 90px -50px #000;
}

.d3-11__panel {
  display: grid;
  gap: 0.55rem;
}

.d3-11__kicker {
  font: 700 0.64rem/1.4 var(--d3-11-font-display);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--d3-11-acc2);
}

.d3-11__title {
  font: 700 clamp(1.25rem, 4vw, 2rem)/1.08 system-ui, sans-serif;
  letter-spacing: -0.02em;
}

.d3-11__copy {
  margin: 0;
  font: 500 clamp(0.75rem, 2vw, 0.85rem)/1.55 system-ui, sans-serif;
  color: var(--d3-11-mut);
  text-wrap: pretty;
}

.d3-11__copy code {
  font: 600 0.95em/1 var(--d3-11-font-display);
  color: var(--d3-11-acc);
}

.d3-11__read {
  margin: 0.2rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.d3-11__read div {
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--d3-11-line);
  border-radius: 12px;
  background: color-mix(in oklab, var(--d3-11-surf) 70%, transparent);
}

.d3-11__read dt {
  font: 700 0.58rem/1.4 var(--d3-11-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--d3-11-mut);
}

.d3-11__read dd {
  margin: 0;
  font: 600 0.85rem/1.2 var(--d3-11-font-display);
  color: var(--d3-11-ink);
}

.d3-11__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  font: 500 0.7rem/1.2 var(--d3-11-font-display);
  color: var(--d3-11-mut);
}

.d3-11__legend li {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.d3-11__key {
  inline-size: 0.65rem;
  block-size: 0.65rem;
  border-radius: 999px;
}

.d3-11__key--x {
  background: var(--d3-11-acc);
}

.d3-11__key--y {
  background: var(--d3-11-acc2);
}

.d3-11__key--z {
  background: color-mix(in oklab, var(--d3-11-acc) 50%, var(--d3-11-acc2));
}

.d3-11__scene {
  perspective: 1000px;
  perspective-origin: 50% 45%;
  block-size: clamp(15rem, 40vw, 20rem);
  display: grid;
  place-items: center;
}

.d3-11__rig {
  position: relative;
  transform-style: preserve-3d;
  display: grid;
  place-items: center;
}

.d3-11__badge {
  position: relative;
  inline-size: clamp(9rem, 26vw, 12rem);
  aspect-ratio: 0.78;
  padding: clamp(0.8rem, 2.4vw, 1.1rem);
  display: grid;
  align-content: end;
  gap: 0.15rem;
  overflow: hidden;
  border-radius: 18px;
  color: #fff;
  background: linear-gradient(150deg, #2a1c48, #120f1c 60%);
  border: 1px solid color-mix(in oklab, var(--d3-11-acc) 45%, transparent);
  box-shadow: 0 30px 60px -26px #000, inset 0 1px 0 rgba(255, 255, 255, 0.14);
  animation: d3-11-tumble var(--d3-11-spin) linear infinite;
}

@keyframes d3-11-tumble {
  from {
    transform: rotate3d(1, 1, 0.35, 0deg);
  }

  to {
    transform: rotate3d(1, 1, 0.35, 360deg);
  }
}

.d3-11__holo {
  position: absolute;
  inset: -30% -10%;
  background: conic-gradient(from 200deg, transparent, color-mix(in oklab, var(--d3-11-acc) 70%, transparent), color-mix(in oklab, var(--d3-11-acc2) 65%, transparent), transparent 70%);
  opacity: 0.5;
}

.d3-11__mark {
  position: absolute;
  inset-block-start: clamp(0.8rem, 2.4vw, 1.1rem);
  inset-inline-start: clamp(0.8rem, 2.4vw, 1.1rem);
  font: 800 clamp(1.4rem, 4.5vw, 2rem)/1 system-ui, sans-serif;
  letter-spacing: -0.04em;
}

.d3-11__name {
  position: relative;
  font: 700 clamp(0.85rem, 2.4vw, 1rem)/1.1 system-ui, sans-serif;
}

.d3-11__serial {
  position: relative;
  font: 500 0.64rem/1.2 var(--d3-11-font-display);
  color: rgba(255, 255, 255, 0.7);
}

.d3-11__vector {
  position: absolute;
  inline-size: 132%;
  block-size: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--d3-11-acc2), transparent);
  transform: rotateZ(-38deg) translateZ(-1px);
  opacity: 0.8;
}

@media (prefers-reduced-motion: reduce) {
  .d3-11__badge {
    animation: none;
    transform: rotate3d(1, 1, 0.35, 42deg);
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS 3D Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Rotate3d and the Transform Matrix
Source: https://codefronts.com/design-styles/css-3d/rotate3d-and-the-transform-matrix/

rotate3d(x, y, z, angle) rotates around an arbitrary axis vector, which rotateX, rotateY and rotateZ cannot express between them. Four wireframe plates turn around four different vectors with the vector drawn on each, and the readout names the fact that all of it compiles down to one matrix3d().
## HTML
```html
<section class="d3-11" aria-labelledby="d3-11-title">
  <h2 class="d3-11__sr" id="d3-11-title">Rotate3d and the transform matrix</h2>

  <div class="d3-11__theme">
    <input class="d3-11__theme-input" type="checkbox" id="d3-11-theme">
    <label class="d3-11__theme-label" for="d3-11-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
      <span class="d3-11__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="d3-11__stage">
    <div class="d3-11__viewport">
      <div class="d3-11__panel">
        <span class="d3-11__kicker">Season pass &middot; 2027</span>
        <strong class="d3-11__title">Rotating around an arbitrary axis</strong>
        <p class="d3-11__copy">Three separate rotations run one after another. <code>rotate3d(x, y, z, angle)</code> runs once, around a vector you choose &mdash; so the badge tumbles on its true diagonal instead of stepping through three axes.</p>
        <dl class="d3-11__read">
          <div><dt>axis</dt><dd>1, 1, 0.35</dd></div>
          <div><dt>angle</dt><dd>360deg</dd></div>
          <div><dt>matrix</dt><dd>4 &times; 4</dd></div>
        </dl>
        <ul class="d3-11__legend">
          <li><span class="d3-11__key d3-11__key--x"></span>X 1.0</li>
          <li><span class="d3-11__key d3-11__key--y"></span>Y 1.0</li>
          <li><span class="d3-11__key d3-11__key--z"></span>Z 0.35</li>
        </ul>
      </div>

      <div class="d3-11__scene">
        <div class="d3-11__rig">
          <div class="d3-11__badge">
            <span class="d3-11__holo" aria-hidden="true"></span>
            <span class="d3-11__mark">VX</span>
            <span class="d3-11__name">Vector Pass</span>
            <span class="d3-11__serial">No. 0117 / 5000</span>
          </div>
          <span class="d3-11__vector" aria-hidden="true"></span>
        </div>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
.d3-11 {
  --d3-11-bg: #17151c;
  --d3-11-surf: #201d28;
  --d3-11-line: #35303f;
  --d3-11-ink: #f2eff7;
  --d3-11-mut: #a49cb4;
  --d3-11-acc: #a26bff;
  --d3-11-acc: oklch(70% 0.19 296);
  --d3-11-acc2: #4de0d4;
  --d3-11-acc2: oklch(84% 0.13 186);
  --d3-11-spin: 14s;
  --d3-11-font-display: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  width: 100%;
  min-height: 100vh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: radial-gradient(48rem 26rem at 84% -8%, color-mix(in oklab, var(--d3-11-acc) 26%, transparent), transparent 62%), radial-gradient(40rem 22rem at -4% 100%, color-mix(in oklab, var(--d3-11-acc2) 18%, transparent), transparent 60%), var(--d3-11-bg);
  color: var(--d3-11-ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.5;
}

.d3-11 *,
.d3-11 *::before,
.d3-11 *::after {
  box-sizing: border-box;
}

.d3-11__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.d3-11:has(.d3-11__theme-input:checked) {
  --d3-11-bg: #f4f2f8;
  --d3-11-surf: #ffffff;
  --d3-11-line: #ded8e8;
  --d3-11-ink: #15121b;
  --d3-11-mut: #625a70;
  --d3-11-acc: #6a29d6;
  --d3-11-acc: oklch(48% 0.22 296);
  --d3-11-acc2: #007f77;
  --d3-11-acc2: oklch(52% 0.1 186);
}

@media (prefers-color-scheme: light) {
  .d3-11:has(.d3-11__theme-input:checked) {
    --d3-11-bg: #f4f2f8;
    --d3-11-surf: #ffffff;
    --d3-11-line: #ded8e8;
    --d3-11-ink: #15121b;
    --d3-11-mut: #625a70;
    --d3-11-acc: #6a29d6;
    --d3-11-acc: oklch(48% 0.22 296);
    --d3-11-acc2: #007f77;
    --d3-11-acc2: oklch(52% 0.1 186);
  }
}

@media (prefers-color-scheme: dark) {
  .d3-11:has(.d3-11__theme-input:checked) {
    --d3-11-bg: #f4f2f8;
    --d3-11-surf: #ffffff;
    --d3-11-line: #ded8e8;
    --d3-11-ink: #15121b;
    --d3-11-mut: #625a70;
    --d3-11-acc: #6a29d6;
    --d3-11-acc: oklch(48% 0.22 296);
    --d3-11-acc2: #007f77;
    --d3-11-acc2: oklch(52% 0.1 186);
  }
}

.d3-11__theme {
  position: absolute;
  inset-block-start: clamp(0.75rem, 3vw, 1.5rem);
  inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
  z-index: 20;
}

.d3-11__theme-input {
  position: absolute;
  opacity: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  cursor: pointer;
}

.d3-11__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 1px solid var(--d3-11-line);
  border-radius: 14px;
  background: var(--d3-11-surf);
  color: var(--d3-11-ink);
  cursor: pointer;
}

.d3-11__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

.d3-11__theme-input:focus-visible + .d3-11__theme-label {
  outline: 2px solid var(--d3-11-acc);
  outline-offset: 3px;
}

.d3-11__stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  min-block-size: 100vh;
  padding: clamp(3.5rem, 9vw, 5rem) clamp(0.75rem, 4vw, 2.5rem);
}

.d3-11__viewport {
  inline-size: 100%;
  max-inline-size: 62rem;
  overflow: hidden;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  align-items: center;
  gap: clamp(1rem, 3.5vw, 2rem);
  padding: clamp(1rem, 3.5vw, 2rem);
  border: 1px solid var(--d3-11-line);
  border-radius: 26px;
  background: color-mix(in oklab, var(--d3-11-surf) 76%, transparent);
  box-shadow: 0 40px 90px -50px #000;
}

.d3-11__panel {
  display: grid;
  gap: 0.55rem;
}

.d3-11__kicker {
  font: 700 0.64rem/1.4 var(--d3-11-font-display);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--d3-11-acc2);
}

.d3-11__title {
  font: 700 clamp(1.25rem, 4vw, 2rem)/1.08 system-ui, sans-serif;
  letter-spacing: -0.02em;
}

.d3-11__copy {
  margin: 0;
  font: 500 clamp(0.75rem, 2vw, 0.85rem)/1.55 system-ui, sans-serif;
  color: var(--d3-11-mut);
  text-wrap: pretty;
}

.d3-11__copy code {
  font: 600 0.95em/1 var(--d3-11-font-display);
  color: var(--d3-11-acc);
}

.d3-11__read {
  margin: 0.2rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.d3-11__read div {
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--d3-11-line);
  border-radius: 12px;
  background: color-mix(in oklab, var(--d3-11-surf) 70%, transparent);
}

.d3-11__read dt {
  font: 700 0.58rem/1.4 var(--d3-11-font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--d3-11-mut);
}

.d3-11__read dd {
  margin: 0;
  font: 600 0.85rem/1.2 var(--d3-11-font-display);
  color: var(--d3-11-ink);
}

.d3-11__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  font: 500 0.7rem/1.2 var(--d3-11-font-display);
  color: var(--d3-11-mut);
}

.d3-11__legend li {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.d3-11__key {
  inline-size: 0.65rem;
  block-size: 0.65rem;
  border-radius: 999px;
}

.d3-11__key--x {
  background: var(--d3-11-acc);
}

.d3-11__key--y {
  background: var(--d3-11-acc2);
}

.d3-11__key--z {
  background: color-mix(in oklab, var(--d3-11-acc) 50%, var(--d3-11-acc2));
}

.d3-11__scene {
  perspective: 1000px;
  perspective-origin: 50% 45%;
  block-size: clamp(15rem, 40vw, 20rem);
  display: grid;
  place-items: center;
}

.d3-11__rig {
  position: relative;
  transform-style: preserve-3d;
  display: grid;
  place-items: center;
}

.d3-11__badge {
  position: relative;
  inline-size: clamp(9rem, 26vw, 12rem);
  aspect-ratio: 0.78;
  padding: clamp(0.8rem, 2.4vw, 1.1rem);
  display: grid;
  align-content: end;
  gap: 0.15rem;
  overflow: hidden;
  border-radius: 18px;
  color: #fff;
  background: linear-gradient(150deg, #2a1c48, #120f1c 60%);
  border: 1px solid color-mix(in oklab, var(--d3-11-acc) 45%, transparent);
  box-shadow: 0 30px 60px -26px #000, inset 0 1px 0 rgba(255, 255, 255, 0.14);
  animation: d3-11-tumble var(--d3-11-spin) linear infinite;
}

@keyframes d3-11-tumble {
  from {
    transform: rotate3d(1, 1, 0.35, 0deg);
  }

  to {
    transform: rotate3d(1, 1, 0.35, 360deg);
  }
}

.d3-11__holo {
  position: absolute;
  inset: -30% -10%;
  background: conic-gradient(from 200deg, transparent, color-mix(in oklab, var(--d3-11-acc) 70%, transparent), color-mix(in oklab, var(--d3-11-acc2) 65%, transparent), transparent 70%);
  opacity: 0.5;
}

.d3-11__mark {
  position: absolute;
  inset-block-start: clamp(0.8rem, 2.4vw, 1.1rem);
  inset-inline-start: clamp(0.8rem, 2.4vw, 1.1rem);
  font: 800 clamp(1.4rem, 4.5vw, 2rem)/1 system-ui, sans-serif;
  letter-spacing: -0.04em;
}

.d3-11__name {
  position: relative;
  font: 700 clamp(0.85rem, 2.4vw, 1rem)/1.1 system-ui, sans-serif;
}

.d3-11__serial {
  position: relative;
  font: 500 0.64rem/1.2 var(--d3-11-font-display);
  color: rgba(255, 255, 255, 0.7);
}

.d3-11__vector {
  position: absolute;
  inline-size: 132%;
  block-size: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--d3-11-acc2), transparent);
  transform: rotateZ(-38deg) translateZ(-1px);
  opacity: 0.8;
}

@media (prefers-reduced-motion: reduce) {
  .d3-11__badge {
    animation: none;
    transform: rotate3d(1, 1, 0.35, 42deg);
  }
}
```

How this works

rotate3d(x, y, z, angle) takes a direction, not three separate rotations. The first three numbers describe a vector through the transform origin; the element rotates around that line by the given angle. rotate3d(1, 0, 0, 45deg) is exactly rotateX(45deg), and rotate3d(1, 1, 0, 45deg) is a rotation around the diagonal — a tumble you cannot write as any combination of the named functions, because chaining rotateX and rotateY applies them in sequence around moving axes rather than once around a fixed one.

The vector's length does not matter, only its direction. rotate3d(2, 2, 0, 45deg) and rotate3d(1, 1, 0, 45deg) are the same rotation because the browser normalises the vector. That is useful when the numbers come from measurement: feed in a raw direction without scaling it. An all-zero vector is the one invalid case — there is no axis, so the declaration is dropped.

Everything you write becomes one matrix. A transform list of five functions is multiplied into a single 4×4 matrix, which is what DevTools shows you as matrix3d() with sixteen comma-separated numbers. You would rarely author that by hand, but recognising it matters: when a computed style shows matrix3d and you expected your named functions, nothing is wrong — that is just the resolved form. It is also why interpolating between two transform lists with different function counts falls back to matrix interpolation, which can take a visually different path than you intended.

Practical rule: use the named functions until you need an axis they cannot name. rotate3d earns its place for tilted tumbles, for axes derived from data, and for single-declaration rotations that would otherwise need a chain. Keep the origin explicit whenever the axis should not pass through the element's centre — the vector is measured from the transform origin, so moving the origin moves the axis.

Make it yours

  • Change any plate's vector to steer its axis; the drawn axis line reads from the same numbers, so it stays truthful.
  • Scale a vector up, from 1, 1, 0 to 4, 4, 0, to demonstrate that normalisation makes it identical.
  • Set a plate's transform-origin off centre to move the axis rather than the angle.
  • Retune --d3-11-beat to slow the tumble for inspection, or freeze it and step the angle by hand.
  • Add a fifth plate with rotate3d(1, 1, 1, 120deg), the rotation that cycles the three axes.
  • Recolour with the two --d3-11-acc declarations, hex first then oklch.

Gotchas — read before shipping

  • rotate3d(0, 0, 0, 45deg) has no axis and is dropped entirely, so the element simply does not rotate — easy to hit when the vector comes from a variable that resolved to zero.
  • Chaining rotateX then rotateY is not the same as one rotate3d around the diagonal. The chain rotates around axes that have already moved, which is demo 04's rule.
  • Interpolating between transform lists of different lengths falls back to matrix interpolation, so an animation can take an unexpected path even though both endpoints are correct.
  • A hand-written matrix3d() loses all readability and any chance of a sensible diff. Author with named functions and read the matrix only in DevTools.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by :has(), oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.

oklch() and the :has() theme toggle set the floor; rotate3d() and matrix3d() have both been Baseline since 2015 (Chrome 36, Safari 9, Firefox 16) and contribute nothing. Below the oklch() floor the tokens fall back to the sRGB hex declared first and the wireframes are unchanged; without :has() the toggle stops switching while the operating-system colour scheme still resolves.

Techniques used in this demo

Search CodeFronts

Loading…