Pure CSSMIT licensed

Optical Alignment vs Mathematical Centering

A play triangle centred by maths looks left-heavy, and a quotation mark that starts flush looks indented. Each pair here shows the mathematically-centred version beside the optically-corrected one with the nudge value printed, so you can see the correction and know exactly how much of it to apply.

Live Demo
Plain CSSTailwind v4
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="mu-04" aria-labelledby="mu-04-title">
  <h2 class="mu-04__sr" id="mu-04-title">Optical alignment examples</h2>
  <div class="mu-04__theme">
    <input class="mu-04__theme-input" type="checkbox" id="mu-04-theme">
    <label class="mu-04__theme-label" for="mu-04-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
      <span class="mu-04__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="mu-04__stage">

    <div class="mu-04__grid">
      <article class="mu-04__case" aria-labelledby="mu-04-c1">
        <h3 class="mu-04__case-title" id="mu-04-c1">Play triangle in a round button</h3>
        <div class="mu-04__demo">
          <div class="mu-04__slot">
            <button class="mu-04__btn mu-04__btn--guide" type="button">
              <svg class="mu-04__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5.5v13l11-6.5z" fill="currentColor"/></svg>
              <span class="mu-04__sr">Play &mdash; mathematically centred</span>
            </button>
            <p class="mu-04__label">Mathematical<span>translate: 0</span></p>
          </div>
          <div class="mu-04__slot">
            <button class="mu-04__btn mu-04__btn--guide mu-04__btn--fixed" type="button">
              <svg class="mu-04__icon mu-04__icon--nudged" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5.5v13l11-6.5z" fill="currentColor"/></svg>
              <span class="mu-04__sr">Play &mdash; optically corrected</span>
            </button>
            <p class="mu-04__label">Optical<span>translateX(0.06em)</span></p>
          </div>
        </div>
      </article>

      <article class="mu-04__case" aria-labelledby="mu-04-c2">
        <h3 class="mu-04__case-title" id="mu-04-c2">Opening quotation mark</h3>
        <div class="mu-04__demo mu-04__demo--stack">
          <div class="mu-04__slot mu-04__slot--wide">
            <blockquote class="mu-04__quote">
              <p>Restraint is a budget, not a mood.</p>
            </blockquote>
            <p class="mu-04__label">Mathematical<span>text-indent: 0</span></p>
          </div>
          <div class="mu-04__slot mu-04__slot--wide">
            <blockquote class="mu-04__quote mu-04__quote--hang">
              <p>Restraint is a budget, not a mood.</p>
            </blockquote>
            <p class="mu-04__label">Optical<span>text-indent: &minus;0.42em</span></p>
          </div>
        </div>
      </article>

      <article class="mu-04__case" aria-labelledby="mu-04-c3">
        <h3 class="mu-04__case-title" id="mu-04-c3">Icon beside a label</h3>
        <div class="mu-04__demo">
          <div class="mu-04__slot">
            <span class="mu-04__pill">
              <svg class="mu-04__icon-sm" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12h13m0 0-5-5m5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
              Continue
            </span>
            <p class="mu-04__label">Mathematical<span>baseline aligned</span></p>
          </div>
          <div class="mu-04__slot">
            <span class="mu-04__pill">
              <svg class="mu-04__icon-sm mu-04__icon-sm--nudged" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12h13m0 0-5-5m5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
              Continue
            </span>
            <p class="mu-04__label">Optical<span>translateY(&minus;0.03em)</span></p>
          </div>
        </div>
      </article>
    </div>
  </div>
</section>
.mu-04 {
  --mu-04-unit: 0.5rem;
  --mu-04-icon: 1.75rem;
  --mu-04-target: 44px;
  --mu-04-nudge: 0.06em;
  --mu-04-font: ui-rounded, "SF Pro Rounded", "Hiragino Maru Gothic ProN", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mu-04-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --mu-04-bg: #08090a;
  --mu-04-bg: oklch(13% 0.004 250);
  --mu-04-surface: #121416;
  --mu-04-surface: oklch(19% 0.005 250);
  --mu-04-ink: #f4f6f7;
  --mu-04-ink: oklch(97% 0.002 250);
  --mu-04-ink-2: #a8b0b6;
  --mu-04-ink-2: oklch(75% 0.008 250);
  --mu-04-line: #2b3034;
  --mu-04-line: oklch(29% 0.007 250);
  --mu-04-guide: #6b747a;
  --mu-04-guide: oklch(56% 0.01 250);
  --mu-04-acc: #6ee7b7;
  --mu-04-acc: oklch(85% 0.13 165);
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--mu-04-bg);
  color: var(--mu-04-ink);
  font-family: var(--mu-04-font);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

.mu-04 *,
.mu-04 *::before,
.mu-04 *::after {
  box-sizing: border-box;
}

@media (prefers-color-scheme: light) {
  .mu-04 {
    --mu-04-bg: #f3f5f6;
    --mu-04-bg: oklch(96% 0.003 250);
    --mu-04-surface: #ffffff;
    --mu-04-surface: oklch(100% 0 0);
    --mu-04-ink: #14171a;
    --mu-04-ink: oklch(19% 0.006 250);
    --mu-04-ink-2: #545c62;
    --mu-04-ink-2: oklch(45% 0.011 250);
    --mu-04-line: #d3d8db;
    --mu-04-line: oklch(86% 0.005 250);
    --mu-04-guide: #8d959a;
    --mu-04-guide: oklch(66% 0.008 250);
    --mu-04-acc: #05704f;
    --mu-04-acc: oklch(45% 0.11 165);
  }
}

.mu-04[data-theme="light"],
.mu-04:has(.mu-04__theme-input:checked) {
  --mu-04-bg: #f3f5f6;
  --mu-04-bg: oklch(96% 0.003 250);
  --mu-04-surface: #ffffff;
  --mu-04-surface: oklch(100% 0 0);
  --mu-04-ink: #14171a;
  --mu-04-ink: oklch(19% 0.006 250);
  --mu-04-ink-2: #545c62;
  --mu-04-ink-2: oklch(45% 0.011 250);
  --mu-04-line: #d3d8db;
  --mu-04-line: oklch(86% 0.005 250);
  --mu-04-guide: #8d959a;
  --mu-04-guide: oklch(66% 0.008 250);
  --mu-04-acc: #05704f;
  --mu-04-acc: oklch(45% 0.11 165);
}

@media (prefers-color-scheme: dark) {
  .mu-04:has(.mu-04__theme-input:checked) {
    --mu-04-bg: #f3f5f6;
    --mu-04-bg: oklch(96% 0.003 250);
    --mu-04-surface: #ffffff;
    --mu-04-surface: oklch(100% 0 0);
    --mu-04-ink: #14171a;
    --mu-04-ink: oklch(19% 0.006 250);
    --mu-04-ink-2: #545c62;
    --mu-04-ink-2: oklch(45% 0.011 250);
    --mu-04-line: #d3d8db;
    --mu-04-line: oklch(86% 0.005 250);
    --mu-04-guide: #8d959a;
    --mu-04-guide: oklch(66% 0.008 250);
    --mu-04-acc: #05704f;
    --mu-04-acc: oklch(45% 0.11 165);
  }
}

@media (prefers-color-scheme: light) {
  .mu-04:has(.mu-04__theme-input:checked) {
    --mu-04-bg: #08090a;
    --mu-04-bg: oklch(13% 0.004 250);
    --mu-04-surface: #121416;
    --mu-04-surface: oklch(19% 0.005 250);
    --mu-04-ink: #f4f6f7;
    --mu-04-ink: oklch(97% 0.002 250);
    --mu-04-ink-2: #a8b0b6;
    --mu-04-ink-2: oklch(75% 0.008 250);
    --mu-04-line: #2b3034;
    --mu-04-line: oklch(29% 0.007 250);
    --mu-04-guide: #6b747a;
    --mu-04-guide: oklch(56% 0.01 250);
    --mu-04-acc: #6ee7b7;
    --mu-04-acc: oklch(85% 0.13 165);
  }
}

.mu-04__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.mu-04__theme {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  z-index: 5;
}

.mu-04__theme-input {
  position: absolute;
  inset: 0;
  inline-size: 44px;
  block-size: 44px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.mu-04__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 1px solid var(--mu-04-line);
  border-radius: 50%;
  background: var(--mu-04-surface);
  color: var(--mu-04-ink-2);
  cursor: pointer;
}

.mu-04__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

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

.mu-04__stage {
  max-inline-size: 68rem;
  margin-inline: auto;
  padding: calc(var(--mu-04-unit) * 8) calc(var(--mu-04-unit) * 3) calc(var(--mu-04-unit) * 10);
  display: grid;
  gap: calc(var(--mu-04-unit) * 6);
  grid-template-columns: minmax(0, 1fr);
}

.mu-04__grid {
  display: grid;
  gap: calc(var(--mu-04-unit) * 3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
}

.mu-04__case {
  min-inline-size: 0;
  display: grid;
  gap: calc(var(--mu-04-unit) * 2.5);
  align-content: start;
  padding: calc(var(--mu-04-unit) * 3);
  background: var(--mu-04-surface);
  border-radius: 8px;
}

.mu-04__case-title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  min-inline-size: 0;
}

.mu-04__demo {
  display: grid;
  gap: calc(var(--mu-04-unit) * 2);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.mu-04__demo--stack {
  grid-template-columns: minmax(0, 1fr);
}

.mu-04__slot {
  min-inline-size: 0;
  display: grid;
  gap: var(--mu-04-unit);
  justify-items: start;
}

.mu-04__slot--wide {
  justify-items: stretch;
}

.mu-04__btn {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--mu-04-target);
  block-size: var(--mu-04-target);
  min-inline-size: var(--mu-04-target);
  min-block-size: var(--mu-04-target);
  border: 1px solid var(--mu-04-line);
  border-radius: 50%;
  background: transparent;
  color: var(--mu-04-ink);
  cursor: pointer;
  padding: 0;
  transition: border-color 160ms ease, color 160ms ease;
}

.mu-04__btn:hover {
  border-color: var(--mu-04-acc);
  color: var(--mu-04-acc);
}

.mu-04__btn:focus-visible {
  outline: 2px solid var(--mu-04-acc);
  outline-offset: 2px;
}

.mu-04__btn--guide::after {
  content: "";
  position: absolute;
  inset-block: 6px;
  inset-inline-start: 50%;
  inline-size: 0;
  border-inline-start: 1px dashed var(--mu-04-guide);
  pointer-events: none;
}

.mu-04__btn--fixed {
  border-color: var(--mu-04-acc);
}

.mu-04__icon {
  inline-size: var(--mu-04-icon);
  block-size: var(--mu-04-icon);
}

.mu-04__icon--nudged {
  translate: var(--mu-04-nudge) 0;
}

.mu-04__label {
  margin: 0;
  font-family: var(--mu-04-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mu-04-ink);
  display: grid;
  gap: 1px;
  min-inline-size: 0;
}

.mu-04__label span {
  text-transform: none;
  letter-spacing: 0;
  color: var(--mu-04-ink-2);
  overflow-wrap: break-word;
}

.mu-04__quote {
  margin: 0;
  padding: calc(var(--mu-04-unit) * 1.5) 0;
  border-block-start: 1px solid var(--mu-04-line);
  min-inline-size: 0;
}

.mu-04__quote p {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.35;
  letter-spacing: -0.012em;
  text-wrap: pretty;
}

.mu-04__quote p::before {
  content: "\201C";
}

.mu-04__quote p::after {
  content: "\201D";
}

.mu-04__quote--hang p {
  text-indent: -0.42em;
  hanging-punctuation: first;
}

.mu-04__pill {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--mu-04-unit) * 1.25);
  min-block-size: var(--mu-04-target);
  padding-inline: calc(var(--mu-04-unit) * 2);
  border: 1px solid var(--mu-04-line);
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 500;
  min-inline-size: 0;
}

.mu-04__icon-sm {
  inline-size: 18px;
  block-size: 18px;
  flex: none;
}

.mu-04__icon-sm--nudged {
  translate: 0 -0.03em;
}

@media (prefers-reduced-motion: reduce) {
  .mu-04__btn {
    transition: none;
  }
}

@media (min-width: 64rem) {
  .mu-04__stage {
    padding-inline: calc(var(--mu-04-unit) * 6);
  }
}
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 Minimalist UI 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: Optical Alignment vs Mathematical Centering
Source: https://codefronts.com/design-styles/css-minimalist-ui/optical-alignment-vs-mathematical-centering/

A play triangle centred by maths looks left-heavy, and a quotation mark that starts flush looks indented. Each pair here shows the mathematically-centred version beside the optically-corrected one with the nudge value printed, so you can see the correction and know exactly how much of it to apply.
## HTML
```html
<section class="mu-04" aria-labelledby="mu-04-title">
  <h2 class="mu-04__sr" id="mu-04-title">Optical alignment examples</h2>
  <div class="mu-04__theme">
    <input class="mu-04__theme-input" type="checkbox" id="mu-04-theme">
    <label class="mu-04__theme-label" for="mu-04-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
      <span class="mu-04__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="mu-04__stage">

    <div class="mu-04__grid">
      <article class="mu-04__case" aria-labelledby="mu-04-c1">
        <h3 class="mu-04__case-title" id="mu-04-c1">Play triangle in a round button</h3>
        <div class="mu-04__demo">
          <div class="mu-04__slot">
            <button class="mu-04__btn mu-04__btn--guide" type="button">
              <svg class="mu-04__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5.5v13l11-6.5z" fill="currentColor"/></svg>
              <span class="mu-04__sr">Play &mdash; mathematically centred</span>
            </button>
            <p class="mu-04__label">Mathematical<span>translate: 0</span></p>
          </div>
          <div class="mu-04__slot">
            <button class="mu-04__btn mu-04__btn--guide mu-04__btn--fixed" type="button">
              <svg class="mu-04__icon mu-04__icon--nudged" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5.5v13l11-6.5z" fill="currentColor"/></svg>
              <span class="mu-04__sr">Play &mdash; optically corrected</span>
            </button>
            <p class="mu-04__label">Optical<span>translateX(0.06em)</span></p>
          </div>
        </div>
      </article>

      <article class="mu-04__case" aria-labelledby="mu-04-c2">
        <h3 class="mu-04__case-title" id="mu-04-c2">Opening quotation mark</h3>
        <div class="mu-04__demo mu-04__demo--stack">
          <div class="mu-04__slot mu-04__slot--wide">
            <blockquote class="mu-04__quote">
              <p>Restraint is a budget, not a mood.</p>
            </blockquote>
            <p class="mu-04__label">Mathematical<span>text-indent: 0</span></p>
          </div>
          <div class="mu-04__slot mu-04__slot--wide">
            <blockquote class="mu-04__quote mu-04__quote--hang">
              <p>Restraint is a budget, not a mood.</p>
            </blockquote>
            <p class="mu-04__label">Optical<span>text-indent: &minus;0.42em</span></p>
          </div>
        </div>
      </article>

      <article class="mu-04__case" aria-labelledby="mu-04-c3">
        <h3 class="mu-04__case-title" id="mu-04-c3">Icon beside a label</h3>
        <div class="mu-04__demo">
          <div class="mu-04__slot">
            <span class="mu-04__pill">
              <svg class="mu-04__icon-sm" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12h13m0 0-5-5m5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
              Continue
            </span>
            <p class="mu-04__label">Mathematical<span>baseline aligned</span></p>
          </div>
          <div class="mu-04__slot">
            <span class="mu-04__pill">
              <svg class="mu-04__icon-sm mu-04__icon-sm--nudged" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 12h13m0 0-5-5m5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
              Continue
            </span>
            <p class="mu-04__label">Optical<span>translateY(&minus;0.03em)</span></p>
          </div>
        </div>
      </article>
    </div>
  </div>
</section>
```
## CSS
```css
.mu-04 {
  --mu-04-unit: 0.5rem;
  --mu-04-icon: 1.75rem;
  --mu-04-target: 44px;
  --mu-04-nudge: 0.06em;
  --mu-04-font: ui-rounded, "SF Pro Rounded", "Hiragino Maru Gothic ProN", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mu-04-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --mu-04-bg: #08090a;
  --mu-04-bg: oklch(13% 0.004 250);
  --mu-04-surface: #121416;
  --mu-04-surface: oklch(19% 0.005 250);
  --mu-04-ink: #f4f6f7;
  --mu-04-ink: oklch(97% 0.002 250);
  --mu-04-ink-2: #a8b0b6;
  --mu-04-ink-2: oklch(75% 0.008 250);
  --mu-04-line: #2b3034;
  --mu-04-line: oklch(29% 0.007 250);
  --mu-04-guide: #6b747a;
  --mu-04-guide: oklch(56% 0.01 250);
  --mu-04-acc: #6ee7b7;
  --mu-04-acc: oklch(85% 0.13 165);
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--mu-04-bg);
  color: var(--mu-04-ink);
  font-family: var(--mu-04-font);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

.mu-04 *,
.mu-04 *::before,
.mu-04 *::after {
  box-sizing: border-box;
}

@media (prefers-color-scheme: light) {
  .mu-04 {
    --mu-04-bg: #f3f5f6;
    --mu-04-bg: oklch(96% 0.003 250);
    --mu-04-surface: #ffffff;
    --mu-04-surface: oklch(100% 0 0);
    --mu-04-ink: #14171a;
    --mu-04-ink: oklch(19% 0.006 250);
    --mu-04-ink-2: #545c62;
    --mu-04-ink-2: oklch(45% 0.011 250);
    --mu-04-line: #d3d8db;
    --mu-04-line: oklch(86% 0.005 250);
    --mu-04-guide: #8d959a;
    --mu-04-guide: oklch(66% 0.008 250);
    --mu-04-acc: #05704f;
    --mu-04-acc: oklch(45% 0.11 165);
  }
}

.mu-04[data-theme="light"],
.mu-04:has(.mu-04__theme-input:checked) {
  --mu-04-bg: #f3f5f6;
  --mu-04-bg: oklch(96% 0.003 250);
  --mu-04-surface: #ffffff;
  --mu-04-surface: oklch(100% 0 0);
  --mu-04-ink: #14171a;
  --mu-04-ink: oklch(19% 0.006 250);
  --mu-04-ink-2: #545c62;
  --mu-04-ink-2: oklch(45% 0.011 250);
  --mu-04-line: #d3d8db;
  --mu-04-line: oklch(86% 0.005 250);
  --mu-04-guide: #8d959a;
  --mu-04-guide: oklch(66% 0.008 250);
  --mu-04-acc: #05704f;
  --mu-04-acc: oklch(45% 0.11 165);
}

@media (prefers-color-scheme: dark) {
  .mu-04:has(.mu-04__theme-input:checked) {
    --mu-04-bg: #f3f5f6;
    --mu-04-bg: oklch(96% 0.003 250);
    --mu-04-surface: #ffffff;
    --mu-04-surface: oklch(100% 0 0);
    --mu-04-ink: #14171a;
    --mu-04-ink: oklch(19% 0.006 250);
    --mu-04-ink-2: #545c62;
    --mu-04-ink-2: oklch(45% 0.011 250);
    --mu-04-line: #d3d8db;
    --mu-04-line: oklch(86% 0.005 250);
    --mu-04-guide: #8d959a;
    --mu-04-guide: oklch(66% 0.008 250);
    --mu-04-acc: #05704f;
    --mu-04-acc: oklch(45% 0.11 165);
  }
}

@media (prefers-color-scheme: light) {
  .mu-04:has(.mu-04__theme-input:checked) {
    --mu-04-bg: #08090a;
    --mu-04-bg: oklch(13% 0.004 250);
    --mu-04-surface: #121416;
    --mu-04-surface: oklch(19% 0.005 250);
    --mu-04-ink: #f4f6f7;
    --mu-04-ink: oklch(97% 0.002 250);
    --mu-04-ink-2: #a8b0b6;
    --mu-04-ink-2: oklch(75% 0.008 250);
    --mu-04-line: #2b3034;
    --mu-04-line: oklch(29% 0.007 250);
    --mu-04-guide: #6b747a;
    --mu-04-guide: oklch(56% 0.01 250);
    --mu-04-acc: #6ee7b7;
    --mu-04-acc: oklch(85% 0.13 165);
  }
}

.mu-04__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.mu-04__theme {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  z-index: 5;
}

.mu-04__theme-input {
  position: absolute;
  inset: 0;
  inline-size: 44px;
  block-size: 44px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.mu-04__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 1px solid var(--mu-04-line);
  border-radius: 50%;
  background: var(--mu-04-surface);
  color: var(--mu-04-ink-2);
  cursor: pointer;
}

.mu-04__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

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

.mu-04__stage {
  max-inline-size: 68rem;
  margin-inline: auto;
  padding: calc(var(--mu-04-unit) * 8) calc(var(--mu-04-unit) * 3) calc(var(--mu-04-unit) * 10);
  display: grid;
  gap: calc(var(--mu-04-unit) * 6);
  grid-template-columns: minmax(0, 1fr);
}

.mu-04__grid {
  display: grid;
  gap: calc(var(--mu-04-unit) * 3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
}

.mu-04__case {
  min-inline-size: 0;
  display: grid;
  gap: calc(var(--mu-04-unit) * 2.5);
  align-content: start;
  padding: calc(var(--mu-04-unit) * 3);
  background: var(--mu-04-surface);
  border-radius: 8px;
}

.mu-04__case-title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  min-inline-size: 0;
}

.mu-04__demo {
  display: grid;
  gap: calc(var(--mu-04-unit) * 2);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.mu-04__demo--stack {
  grid-template-columns: minmax(0, 1fr);
}

.mu-04__slot {
  min-inline-size: 0;
  display: grid;
  gap: var(--mu-04-unit);
  justify-items: start;
}

.mu-04__slot--wide {
  justify-items: stretch;
}

.mu-04__btn {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--mu-04-target);
  block-size: var(--mu-04-target);
  min-inline-size: var(--mu-04-target);
  min-block-size: var(--mu-04-target);
  border: 1px solid var(--mu-04-line);
  border-radius: 50%;
  background: transparent;
  color: var(--mu-04-ink);
  cursor: pointer;
  padding: 0;
  transition: border-color 160ms ease, color 160ms ease;
}

.mu-04__btn:hover {
  border-color: var(--mu-04-acc);
  color: var(--mu-04-acc);
}

.mu-04__btn:focus-visible {
  outline: 2px solid var(--mu-04-acc);
  outline-offset: 2px;
}

.mu-04__btn--guide::after {
  content: "";
  position: absolute;
  inset-block: 6px;
  inset-inline-start: 50%;
  inline-size: 0;
  border-inline-start: 1px dashed var(--mu-04-guide);
  pointer-events: none;
}

.mu-04__btn--fixed {
  border-color: var(--mu-04-acc);
}

.mu-04__icon {
  inline-size: var(--mu-04-icon);
  block-size: var(--mu-04-icon);
}

.mu-04__icon--nudged {
  translate: var(--mu-04-nudge) 0;
}

.mu-04__label {
  margin: 0;
  font-family: var(--mu-04-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mu-04-ink);
  display: grid;
  gap: 1px;
  min-inline-size: 0;
}

.mu-04__label span {
  text-transform: none;
  letter-spacing: 0;
  color: var(--mu-04-ink-2);
  overflow-wrap: break-word;
}

.mu-04__quote {
  margin: 0;
  padding: calc(var(--mu-04-unit) * 1.5) 0;
  border-block-start: 1px solid var(--mu-04-line);
  min-inline-size: 0;
}

.mu-04__quote p {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.35;
  letter-spacing: -0.012em;
  text-wrap: pretty;
}

.mu-04__quote p::before {
  content: "\201C";
}

.mu-04__quote p::after {
  content: "\201D";
}

.mu-04__quote--hang p {
  text-indent: -0.42em;
  hanging-punctuation: first;
}

.mu-04__pill {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--mu-04-unit) * 1.25);
  min-block-size: var(--mu-04-target);
  padding-inline: calc(var(--mu-04-unit) * 2);
  border: 1px solid var(--mu-04-line);
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 500;
  min-inline-size: 0;
}

.mu-04__icon-sm {
  inline-size: 18px;
  block-size: 18px;
  flex: none;
}

.mu-04__icon-sm--nudged {
  translate: 0 -0.03em;
}

@media (prefers-reduced-motion: reduce) {
  .mu-04__btn {
    transition: none;
  }
}

@media (min-width: 64rem) {
  .mu-04__stage {
    padding-inline: calc(var(--mu-04-unit) * 6);
  }
}
```

How this works

Mathematical centring balances bounding boxes; your eye balances mass. A play triangle’s bounding box is a rectangle, but its visual weight sits toward the flat left edge, so a perfectly centred triangle reads as sitting too far left. The fix is a small translation — typically 4% to 8% of the icon size — toward the point. Same story for any asymmetric shape: send, chevron, external-link.

Punctuation hangs. An opening quotation mark or a bullet has most of its bounding box empty, so text that starts with one looks indented relative to the paragraph above it. hanging-punctuation: first solves this natively in WebKit; elsewhere a negative text-indent of about 0.4em on the quoting element achieves the same optical result. Either way the visible left edge of the text becomes straight, which is the only edge a reader perceives.

Express the nudge in em, never in px. A 1px correction that looks right on a 16px icon is invisible at 32px and enormous at 12px. In em the correction scales with the thing it is correcting, so one value serves every size the component ships at.

The trap: these are per-glyph and per-icon corrections. There is no global rule that fixes optical alignment, and a site-wide “nudge all icons 2px right” utility will break every symmetrical icon you own. The correction belongs in the component, next to the shape it corrects, with a comment saying why. If you cannot state which visual mass you are compensating for, do not apply a nudge — you are just moving things until they feel different.

Make it yours

  • Retune --mu-04-nudge per icon; 0.04em to 0.08em covers most triangles, and symmetrical icons need 0.
  • Change the guide colour with --mu-04-guide, or delete the ::after rule to ship the corrected controls without the teaching overlay.
  • Swap hanging-punctuation for a text-indent: -0.42em on the blockquote if you need identical output in every engine.
  • Scale the whole artefact with --mu-04-icon; the em-based nudges follow automatically.
  • Set --mu-04-target above 44px for glove or kiosk use — the correction is unaffected.
  • Recolour by editing the two --mu-04-acc declarations, hex first then oklch.

Gotchas — read before shipping

  • Applying an optical nudge globally to all icons breaks every symmetrical one. The correction is per-shape and belongs inside the component.
  • Nudging with margin instead of translate changes layout and can shift the hit area off the visual target. Transform-based corrections move pixels, not boxes.
  • A px-based nudge is wrong at every size except the one you tuned it at. Use em.
  • Correcting the icon by shrinking the button is the worst possible fix: it fails SC 2.5.8 the moment the target drops below 44×44px.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by :has(), oklch(), 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 for the styling; the nudge mechanism is plain translate and has been Baseline for a decade. hanging-punctuation ships in Safari 10 but is not implemented in Chrome or Firefox, so it is applied as a progressive enhancement with a text-indent fallback declared alongside — the optical result is the same in every engine, only the mechanism differs.

Techniques used in this demo

Search CodeFronts

Loading…