Pure CSSMIT licensed

Risograph Print Misregistration Texture

Risograph printing is cheap, slightly wrong, and beloved for exactly that. The look is two spot inks printed in separate passes that never line up perfectly, over-printing where they overlap and showing paper where they miss. This is a full event poster built from that mechanism: two offset ink layers, multiply overprint, and grain that belongs to the paper rather than the ink.

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="grn-17" aria-labelledby="grn-17-title">
  <div class="grn-17__theme">
    <input class="grn-17__theme-input" type="checkbox" id="grn-17-theme">
    <label class="grn-17__theme-label" for="grn-17-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="grn-17__sr">Toggle dark theme</span>
    </label>
  </div>

  <main class="grn-17__shell">
    <article class="grn-17__poster">
      <span class="grn-17__ink grn-17__ink--a" aria-hidden="true"></span>
      <span class="grn-17__ink grn-17__ink--b" aria-hidden="true"></span>
      <span class="grn-17__paper" aria-hidden="true"></span>

      <div class="grn-17__poster-body">
        <p class="grn-17__pre">Bracken Press · Open studio</p>
        <h1 class="grn-17__h1" id="grn-17-title">
          <span class="grn-17__mis" data-grn-17-text="Two Inks">Two Inks</span>
          <span class="grn-17__mis" data-grn-17-text="One Pass">One Pass</span>
          <span class="grn-17__mis" data-grn-17-text="Slightly Wrong">Slightly Wrong</span>
        </h1>
        <p class="grn-17__lede">Fourteen printers, one drum, and a paper feed that has never once lined up. Two nights of riso, screen and letterpress in the Sackville Yard workshops.</p>
        <dl class="grn-17__facts">
          <div><dt>When</dt><dd>19–20 June, 6pm</dd></div>
          <div><dt>Where</dt><dd>Unit 7, Sackville Yard</dd></div>
          <div><dt>Entry</dt><dd>Free, bring paper</dd></div>
        </dl>
        <a class="grn-17__cta" href="#grn-17-title">Reserve a press slot</a>
      </div>
    </article>

    <section class="grn-17__notes" aria-labelledby="grn-17-h-notes">
      <h2 class="grn-17__h2" id="grn-17-h-notes">How the misregistration is built</h2>
      <ol class="grn-17__steps">
        <li><b>Duplicate the shape set.</b> Two absolutely positioned layers, identical geometry.</li>
        <li><b>Offset each by a few pixels.</b> Different directions. 2–5px reads as print; 8px reads as a bug.</li>
        <li><b>Blend both in multiply.</b> Overlaps darken into a third colour, exactly like overprinted ink.</li>
        <li><b>Put the grain on top.</b> It is the paper showing through, not dust on the ink.</li>
      </ol>
    </section>
  </main>
</section>
.grn-17 {
  --grn-17-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='190' height='190'%3E%3Cfilter id='grn17n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='190' height='190' filter='url(%23grn17n)'/%3E%3C/svg%3E");
  --grn-17-paper: #f6f1e4;
  --grn-17-paper: oklch(95% 0.02 90);
  --grn-17-ink-a: #ff48a0;
  --grn-17-ink-a: oklch(70% 0.28 5);
  --grn-17-ink-b: #1f4bd8;
  --grn-17-ink-b: oklch(48% 0.24 265);
  --grn-17-ink: #14110c;
  --grn-17-mut: #6a6152;
  --grn-17-line: #ddd4bf;
  --grn-17-shift: 4px;
  --grn-17-grain: 0.09;
  --grn-17-font-display: "Archivo Black", "Anton", "Helvetica Neue", Impact, sans-serif;
  --grn-17-font-mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", Menlo, monospace;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--grn-17-paper);
  color: var(--grn-17-ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.55;
}

.grn-17 *,
.grn-17 *::before,
.grn-17 *::after {
  box-sizing: border-box;
}

.grn-17 :focus-visible {
  outline: 2px solid var(--grn-17-ink-b);
  outline-offset: 3px;
}

.grn-17 code {
  font-family: var(--grn-17-font-mono);
  font-size: 0.9em;
}

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

.grn-17:has(.grn-17__theme-input:checked) {
  --grn-17-paper: #14120d;
  --grn-17-paper: oklch(18% 0.01 90);
  --grn-17-ink: #f5efe1;
  --grn-17-mut: #a49a86;
  --grn-17-line: #322c22;
  --grn-17-ink-a: #ff5fae;
  --grn-17-ink-a: oklch(74% 0.26 3);
  --grn-17-ink-b: #4d79ff;
  --grn-17-ink-b: oklch(62% 0.22 268);
  --grn-17-grain: 0.12;
}

@media (prefers-color-scheme: dark) {
  .grn-17 {
    --grn-17-paper: #14120d;
    --grn-17-paper: oklch(18% 0.01 90);
    --grn-17-ink: #f5efe1;
    --grn-17-mut: #a49a86;
    --grn-17-line: #322c22;
    --grn-17-ink-a: #ff5fae;
    --grn-17-ink-a: oklch(74% 0.26 3);
    --grn-17-ink-b: #4d79ff;
    --grn-17-ink-b: oklch(62% 0.22 268);
    --grn-17-grain: 0.12;
  }

  .grn-17:has(.grn-17__theme-input:checked) {
    --grn-17-paper: #f6f1e4;
    --grn-17-paper: oklch(95% 0.02 90);
    --grn-17-ink: #14110c;
    --grn-17-mut: #6a6152;
    --grn-17-line: #ddd4bf;
    --grn-17-ink-a: #ff48a0;
    --grn-17-ink-a: oklch(70% 0.28 5);
    --grn-17-ink-b: #1f4bd8;
    --grn-17-ink-b: oklch(48% 0.24 265);
    --grn-17-grain: 0.09;
  }
}

@media (prefers-color-scheme: light) {
  .grn-17:has(.grn-17__theme-input:checked) {
    --grn-17-paper: #14120d;
    --grn-17-paper: oklch(18% 0.01 90);
    --grn-17-ink: #f5efe1;
    --grn-17-mut: #a49a86;
    --grn-17-line: #322c22;
    --grn-17-ink-a: #ff5fae;
    --grn-17-ink-a: oklch(74% 0.26 3);
    --grn-17-ink-b: #4d79ff;
    --grn-17-ink-b: oklch(62% 0.22 268);
    --grn-17-grain: 0.12;
  }
}

.grn-17__theme {
  position: absolute;
  inset-block-start: clamp(1rem, 3vw, 1.75rem);
  inset-inline-end: clamp(0.85rem, 3vw, 1.75rem);
  z-index: 60;
}

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

.grn-17__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 2px solid var(--grn-17-ink);
  border-radius: 0;
  background: var(--grn-17-paper);
  color: var(--grn-17-ink);
  cursor: pointer;
}

.grn-17__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

.grn-17__shell {
  inline-size: min(70rem, calc(100% - clamp(1.5rem, 5vw, 4rem)));
  margin-inline: auto;
  padding-block: clamp(2rem, 6vw, 3.5rem) clamp(2.5rem, 6vw, 4rem);
  display: grid;
  gap: clamp(1.8rem, 4.5vw, 2.8rem);
}

.grn-17__poster {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border: 2px solid var(--grn-17-ink);
  background: var(--grn-17-paper);
  min-block-size: min(80svh, 44rem);
  display: grid;
  align-items: end;
}

.grn-17__ink {
  position: absolute;
  inset: -8%;
  pointer-events: none;
  mix-blend-mode: multiply;
  background-repeat: no-repeat;
}

.grn-17__ink--a {
  translate: calc(var(--grn-17-shift) * -1) calc(var(--grn-17-shift) * 0.6);
  opacity: 0.92;
  background-image: radial-gradient(38% 30% at 74% 16%, var(--grn-17-ink-a) 0 62%, transparent 64%), radial-gradient(26% 22% at 18% 76%, var(--grn-17-ink-a) 0 60%, transparent 62%), linear-gradient(178deg, transparent 0 72%, var(--grn-17-ink-a) 72% 80%, transparent 80%);
}

.grn-17__ink--b {
  translate: var(--grn-17-shift) calc(var(--grn-17-shift) * -0.5);
  rotate: 0.3deg;
  opacity: 0.88;
  background-image: radial-gradient(34% 27% at 66% 22%, var(--grn-17-ink-b) 0 62%, transparent 64%), radial-gradient(30% 24% at 26% 82%, var(--grn-17-ink-b) 0 60%, transparent 62%), linear-gradient(178deg, transparent 0 74%, var(--grn-17-ink-b) 74% 81%, transparent 81%);
}

.grn-17__paper {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--grn-17-noise);
  opacity: var(--grn-17-grain);
  mix-blend-mode: multiply;
}

.grn-17__poster-body {
  position: relative;
  z-index: 3;
  display: grid;
  gap: 0.9rem;
  padding: clamp(1.5rem, 5vw, 3.2rem);
  max-inline-size: 46rem;
}

.grn-17__pre {
  margin: 0;
  font: 400 0.74rem/1 var(--grn-17-font-mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.grn-17__h1 {
  margin: 0;
  display: grid;
  gap: 0.1rem;
  font: 400 clamp(2.4rem, 9vw, 5.6rem)/0.9 var(--grn-17-font-display);
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

.grn-17__mis {
  position: relative;
  display: block;
  color: var(--grn-17-ink);
  isolation: isolate;
}

.grn-17__mis::before,
.grn-17__mis::after {
  content: attr(data-grn-17-text);
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  pointer-events: none;
  mix-blend-mode: multiply;
}

.grn-17__mis::before {
  color: var(--grn-17-ink-a);
  translate: calc(var(--grn-17-shift) * -0.7) calc(var(--grn-17-shift) * 0.35);
}

.grn-17__mis::after {
  color: var(--grn-17-ink-b);
  translate: calc(var(--grn-17-shift) * 0.7) calc(var(--grn-17-shift) * -0.35);
}

.grn-17__lede {
  margin: 0;
  max-inline-size: 32rem;
  font-size: clamp(0.97rem, 2vw, 1.12rem);
  text-wrap: pretty;
}

.grn-17__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.8rem;
  margin: 0.4rem 0 0;
  padding-block-start: 0.9rem;
  border-block-start: 2px solid var(--grn-17-ink);
}

.grn-17__facts > div {
  display: grid;
  gap: 0.1rem;
}

.grn-17__facts dt {
  margin: 0;
  font: 400 0.66rem/1 var(--grn-17-font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.7;
}

.grn-17__facts dd {
  margin: 0;
  font: 500 0.98rem/1.2 var(--grn-17-font-mono);
}

.grn-17__cta {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  min-block-size: 50px;
  margin-block-start: 0.4rem;
  padding-inline: 1.5rem;
  border: 2px solid var(--grn-17-ink);
  background: var(--grn-17-ink-a);
  color: #14110c;
  text-decoration: none;
  font: 400 0.9rem/1 var(--grn-17-font-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  box-shadow: var(--grn-17-shift) var(--grn-17-shift) 0 var(--grn-17-ink-b);
}

.grn-17__cta:hover {
  translate: 2px 2px;
  box-shadow: 2px 2px 0 var(--grn-17-ink-b);
}

.grn-17__notes {
  display: grid;
  gap: 0.8rem;
  padding: clamp(1.2rem, 3vw, 1.8rem);
  border: 2px solid var(--grn-17-ink);
}

.grn-17__h2 {
  margin: 0;
  font: 400 clamp(1.1rem, 2.8vw, 1.5rem)/1.15 var(--grn-17-font-display);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

.grn-17__steps {
  margin: 0;
  padding-inline-start: 1.4rem;
  display: grid;
  gap: 0.5rem;
  color: var(--grn-17-mut);
  font-size: 0.92rem;
}

.grn-17__steps b {
  color: var(--grn-17-ink);
  font-weight: 600;
}

.grn-17__cta {
  transition: translate 160ms ease, box-shadow 160ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .grn-17__cta {
    transition-duration: 1ms;
  }
}
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 Grain and Noise Texture 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: Risograph Print Misregistration Texture
Source: https://codefronts.com/design-styles/css-grain-texture/risograph-print-misregistration-texture/

Risograph printing is cheap, slightly wrong, and beloved for exactly that. The look is two spot inks printed in separate passes that never line up perfectly, over-printing where they overlap and showing paper where they miss. This is a full event poster built from that mechanism: two offset ink layers, multiply overprint, and grain that belongs to the paper rather than the ink.
## HTML
```html
<section class="grn-17" aria-labelledby="grn-17-title">
  <div class="grn-17__theme">
    <input class="grn-17__theme-input" type="checkbox" id="grn-17-theme">
    <label class="grn-17__theme-label" for="grn-17-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="grn-17__sr">Toggle dark theme</span>
    </label>
  </div>

  <main class="grn-17__shell">
    <article class="grn-17__poster">
      <span class="grn-17__ink grn-17__ink--a" aria-hidden="true"></span>
      <span class="grn-17__ink grn-17__ink--b" aria-hidden="true"></span>
      <span class="grn-17__paper" aria-hidden="true"></span>

      <div class="grn-17__poster-body">
        <p class="grn-17__pre">Bracken Press · Open studio</p>
        <h1 class="grn-17__h1" id="grn-17-title">
          <span class="grn-17__mis" data-grn-17-text="Two Inks">Two Inks</span>
          <span class="grn-17__mis" data-grn-17-text="One Pass">One Pass</span>
          <span class="grn-17__mis" data-grn-17-text="Slightly Wrong">Slightly Wrong</span>
        </h1>
        <p class="grn-17__lede">Fourteen printers, one drum, and a paper feed that has never once lined up. Two nights of riso, screen and letterpress in the Sackville Yard workshops.</p>
        <dl class="grn-17__facts">
          <div><dt>When</dt><dd>19–20 June, 6pm</dd></div>
          <div><dt>Where</dt><dd>Unit 7, Sackville Yard</dd></div>
          <div><dt>Entry</dt><dd>Free, bring paper</dd></div>
        </dl>
        <a class="grn-17__cta" href="#grn-17-title">Reserve a press slot</a>
      </div>
    </article>

    <section class="grn-17__notes" aria-labelledby="grn-17-h-notes">
      <h2 class="grn-17__h2" id="grn-17-h-notes">How the misregistration is built</h2>
      <ol class="grn-17__steps">
        <li><b>Duplicate the shape set.</b> Two absolutely positioned layers, identical geometry.</li>
        <li><b>Offset each by a few pixels.</b> Different directions. 2–5px reads as print; 8px reads as a bug.</li>
        <li><b>Blend both in multiply.</b> Overlaps darken into a third colour, exactly like overprinted ink.</li>
        <li><b>Put the grain on top.</b> It is the paper showing through, not dust on the ink.</li>
      </ol>
    </section>
  </main>
</section>
```
## CSS
```css
.grn-17 {
  --grn-17-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='190' height='190'%3E%3Cfilter id='grn17n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='190' height='190' filter='url(%23grn17n)'/%3E%3C/svg%3E");
  --grn-17-paper: #f6f1e4;
  --grn-17-paper: oklch(95% 0.02 90);
  --grn-17-ink-a: #ff48a0;
  --grn-17-ink-a: oklch(70% 0.28 5);
  --grn-17-ink-b: #1f4bd8;
  --grn-17-ink-b: oklch(48% 0.24 265);
  --grn-17-ink: #14110c;
  --grn-17-mut: #6a6152;
  --grn-17-line: #ddd4bf;
  --grn-17-shift: 4px;
  --grn-17-grain: 0.09;
  --grn-17-font-display: "Archivo Black", "Anton", "Helvetica Neue", Impact, sans-serif;
  --grn-17-font-mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", Menlo, monospace;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--grn-17-paper);
  color: var(--grn-17-ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.55;
}

.grn-17 *,
.grn-17 *::before,
.grn-17 *::after {
  box-sizing: border-box;
}

.grn-17 :focus-visible {
  outline: 2px solid var(--grn-17-ink-b);
  outline-offset: 3px;
}

.grn-17 code {
  font-family: var(--grn-17-font-mono);
  font-size: 0.9em;
}

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

.grn-17:has(.grn-17__theme-input:checked) {
  --grn-17-paper: #14120d;
  --grn-17-paper: oklch(18% 0.01 90);
  --grn-17-ink: #f5efe1;
  --grn-17-mut: #a49a86;
  --grn-17-line: #322c22;
  --grn-17-ink-a: #ff5fae;
  --grn-17-ink-a: oklch(74% 0.26 3);
  --grn-17-ink-b: #4d79ff;
  --grn-17-ink-b: oklch(62% 0.22 268);
  --grn-17-grain: 0.12;
}

@media (prefers-color-scheme: dark) {
  .grn-17 {
    --grn-17-paper: #14120d;
    --grn-17-paper: oklch(18% 0.01 90);
    --grn-17-ink: #f5efe1;
    --grn-17-mut: #a49a86;
    --grn-17-line: #322c22;
    --grn-17-ink-a: #ff5fae;
    --grn-17-ink-a: oklch(74% 0.26 3);
    --grn-17-ink-b: #4d79ff;
    --grn-17-ink-b: oklch(62% 0.22 268);
    --grn-17-grain: 0.12;
  }

  .grn-17:has(.grn-17__theme-input:checked) {
    --grn-17-paper: #f6f1e4;
    --grn-17-paper: oklch(95% 0.02 90);
    --grn-17-ink: #14110c;
    --grn-17-mut: #6a6152;
    --grn-17-line: #ddd4bf;
    --grn-17-ink-a: #ff48a0;
    --grn-17-ink-a: oklch(70% 0.28 5);
    --grn-17-ink-b: #1f4bd8;
    --grn-17-ink-b: oklch(48% 0.24 265);
    --grn-17-grain: 0.09;
  }
}

@media (prefers-color-scheme: light) {
  .grn-17:has(.grn-17__theme-input:checked) {
    --grn-17-paper: #14120d;
    --grn-17-paper: oklch(18% 0.01 90);
    --grn-17-ink: #f5efe1;
    --grn-17-mut: #a49a86;
    --grn-17-line: #322c22;
    --grn-17-ink-a: #ff5fae;
    --grn-17-ink-a: oklch(74% 0.26 3);
    --grn-17-ink-b: #4d79ff;
    --grn-17-ink-b: oklch(62% 0.22 268);
    --grn-17-grain: 0.12;
  }
}

.grn-17__theme {
  position: absolute;
  inset-block-start: clamp(1rem, 3vw, 1.75rem);
  inset-inline-end: clamp(0.85rem, 3vw, 1.75rem);
  z-index: 60;
}

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

.grn-17__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 2px solid var(--grn-17-ink);
  border-radius: 0;
  background: var(--grn-17-paper);
  color: var(--grn-17-ink);
  cursor: pointer;
}

.grn-17__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

.grn-17__shell {
  inline-size: min(70rem, calc(100% - clamp(1.5rem, 5vw, 4rem)));
  margin-inline: auto;
  padding-block: clamp(2rem, 6vw, 3.5rem) clamp(2.5rem, 6vw, 4rem);
  display: grid;
  gap: clamp(1.8rem, 4.5vw, 2.8rem);
}

.grn-17__poster {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border: 2px solid var(--grn-17-ink);
  background: var(--grn-17-paper);
  min-block-size: min(80svh, 44rem);
  display: grid;
  align-items: end;
}

.grn-17__ink {
  position: absolute;
  inset: -8%;
  pointer-events: none;
  mix-blend-mode: multiply;
  background-repeat: no-repeat;
}

.grn-17__ink--a {
  translate: calc(var(--grn-17-shift) * -1) calc(var(--grn-17-shift) * 0.6);
  opacity: 0.92;
  background-image: radial-gradient(38% 30% at 74% 16%, var(--grn-17-ink-a) 0 62%, transparent 64%), radial-gradient(26% 22% at 18% 76%, var(--grn-17-ink-a) 0 60%, transparent 62%), linear-gradient(178deg, transparent 0 72%, var(--grn-17-ink-a) 72% 80%, transparent 80%);
}

.grn-17__ink--b {
  translate: var(--grn-17-shift) calc(var(--grn-17-shift) * -0.5);
  rotate: 0.3deg;
  opacity: 0.88;
  background-image: radial-gradient(34% 27% at 66% 22%, var(--grn-17-ink-b) 0 62%, transparent 64%), radial-gradient(30% 24% at 26% 82%, var(--grn-17-ink-b) 0 60%, transparent 62%), linear-gradient(178deg, transparent 0 74%, var(--grn-17-ink-b) 74% 81%, transparent 81%);
}

.grn-17__paper {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--grn-17-noise);
  opacity: var(--grn-17-grain);
  mix-blend-mode: multiply;
}

.grn-17__poster-body {
  position: relative;
  z-index: 3;
  display: grid;
  gap: 0.9rem;
  padding: clamp(1.5rem, 5vw, 3.2rem);
  max-inline-size: 46rem;
}

.grn-17__pre {
  margin: 0;
  font: 400 0.74rem/1 var(--grn-17-font-mono);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.grn-17__h1 {
  margin: 0;
  display: grid;
  gap: 0.1rem;
  font: 400 clamp(2.4rem, 9vw, 5.6rem)/0.9 var(--grn-17-font-display);
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

.grn-17__mis {
  position: relative;
  display: block;
  color: var(--grn-17-ink);
  isolation: isolate;
}

.grn-17__mis::before,
.grn-17__mis::after {
  content: attr(data-grn-17-text);
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  pointer-events: none;
  mix-blend-mode: multiply;
}

.grn-17__mis::before {
  color: var(--grn-17-ink-a);
  translate: calc(var(--grn-17-shift) * -0.7) calc(var(--grn-17-shift) * 0.35);
}

.grn-17__mis::after {
  color: var(--grn-17-ink-b);
  translate: calc(var(--grn-17-shift) * 0.7) calc(var(--grn-17-shift) * -0.35);
}

.grn-17__lede {
  margin: 0;
  max-inline-size: 32rem;
  font-size: clamp(0.97rem, 2vw, 1.12rem);
  text-wrap: pretty;
}

.grn-17__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.8rem;
  margin: 0.4rem 0 0;
  padding-block-start: 0.9rem;
  border-block-start: 2px solid var(--grn-17-ink);
}

.grn-17__facts > div {
  display: grid;
  gap: 0.1rem;
}

.grn-17__facts dt {
  margin: 0;
  font: 400 0.66rem/1 var(--grn-17-font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.7;
}

.grn-17__facts dd {
  margin: 0;
  font: 500 0.98rem/1.2 var(--grn-17-font-mono);
}

.grn-17__cta {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  min-block-size: 50px;
  margin-block-start: 0.4rem;
  padding-inline: 1.5rem;
  border: 2px solid var(--grn-17-ink);
  background: var(--grn-17-ink-a);
  color: #14110c;
  text-decoration: none;
  font: 400 0.9rem/1 var(--grn-17-font-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  box-shadow: var(--grn-17-shift) var(--grn-17-shift) 0 var(--grn-17-ink-b);
}

.grn-17__cta:hover {
  translate: 2px 2px;
  box-shadow: 2px 2px 0 var(--grn-17-ink-b);
}

.grn-17__notes {
  display: grid;
  gap: 0.8rem;
  padding: clamp(1.2rem, 3vw, 1.8rem);
  border: 2px solid var(--grn-17-ink);
}

.grn-17__h2 {
  margin: 0;
  font: 400 clamp(1.1rem, 2.8vw, 1.5rem)/1.15 var(--grn-17-font-display);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

.grn-17__steps {
  margin: 0;
  padding-inline-start: 1.4rem;
  display: grid;
  gap: 0.5rem;
  color: var(--grn-17-mut);
  font-size: 0.92rem;
}

.grn-17__steps b {
  color: var(--grn-17-ink);
  font-weight: 600;
}

.grn-17__cta {
  transition: translate 160ms ease, box-shadow 160ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .grn-17__cta {
    transition-duration: 1ms;
  }
}
```

How this works

Misregistration is the whole aesthetic. A Risograph prints one colour per pass, and the paper does not return to exactly the same position. So every element that uses two inks shows a small offset — 1 to 4 millimetres — and where the two inks overlap they multiply into a darker third colour. Reproducing this is mechanical: duplicate the element, tint each copy with one ink, offset them by a few pixels, and blend in multiply. Everything else is dressing.

Overprint has to actually multiply. If the two ink layers are opaque, the top one simply hides the bottom one and you get a drop shadow in a silly colour. multiply is what makes the overlap read as ink on ink: pink over blue becomes a deep purple, and the areas where only one ink lands stay bright. Getting this right is the difference between "riso" and "badly aligned duplicate".

The grain belongs to the paper. A common mistake is grain on each ink layer, which makes the inks look dusty. In a real print, the ink is fairly solid and the texture you see is uncoated stock showing through and around it. So the grain goes on top of everything, in multiply at low opacity, tinted slightly warm — and suddenly the whole composition sits on paper instead of on a screen.

Fluorescent inks need a wide gamut. Riso's signature colours — fluorescent pink, bright yellow, federal blue — are outside sRGB. Declaring them in oklch with chroma above 0.2 gets you the real thing on any wide-gamut display, while the sRGB hex fallback keeps it sane everywhere else. This is one of the few places in interface design where wide-gamut colour makes a genuinely visible difference.

Make it yours

  • Change --grn-17-shift to tune the misregistration; 2–5px reads as print, above 8px reads as a mistake.
  • Swap the two ink tokens for any spot pair — pink/blue, yellow/violet and teal/orange are the classic riso combinations.
  • Vary each shape's ink opacity between 0.8 and 1 for uneven drum coverage.
  • Rotate one ink layer by 0.3deg for rotational misregistration as well as translational, which is what a real jam produces.
  • Increase the paper grain to 0.12 for heavily uncoated newsprint stock.
  • Remove the second ink entirely for a single-colour riso, which is both cheaper in real printing and calmer on screen.

Gotchas — read before shipping

  • Opaque ink layers make the effect look like a broken duplicate. multiply is mandatory.
  • Misregistered display type is illegible below about 40px. Use the effect on headlines only and keep body copy in a single ink.
  • Grain on the ink layers rather than above them reads as dust, not paper. Order matters.
  • High-chroma oklch colours clip on sRGB displays and can shift hue noticeably. Always declare a hex fallback first and check both.
  • Two blended full-size layers plus a grain layer is three composites for one decorative element. Keep the effect to a hero or a poster region, not a whole page.

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+.

The technique itself needs only mix-blend-mode and transforms, both Baseline since 2016. Wide-gamut oklch() colours need Chrome 111, Safari 15.4 and Firefox 113 to render at full chroma; below that the hex fallback applies and the inks read as ordinary saturated colours. Display P3 panels show a markedly more fluorescent result than sRGB ones — that is correct, not a bug.

Techniques used in this demo

Search CodeFronts

Loading…