25 CSS Footer Designs15 / 25

Pure CSSMIT licensed

Monospaced Developer Portfolio Footer

A footer for people who read git log for fun: everything set in a monospace grid measured in ch, an ASCII rule, a blinking block caret, and a build stamp that makes the site feel like a deployed artefact rather than a page. Restrained, fast, and no ASCII-art clichés.

Published Updated

Live Demo
Try it

The code

<section class="ft-15" aria-label="Monospaced developer portfolio footer demo">
  <main class="ft-15__main">
    <p class="ft-15__eyebrow">ch units · steps() caret · build stamp</p>
    <h1 class="ft-15__title">footer --format=terminal</h1>
    <p class="ft-15__sub">Every column aligns because the layout is measured in characters, not pixels. The caret blinks on a step curve. The build stamp is the part people actually read.</p>
  </main>
  <footer class="ft-15__foot">
    <div class="ft-15__term">
      <p class="ft-15__rule" aria-hidden="true"></p>
      <div class="ft-15__grid">
        <section class="ft-15__block" aria-labelledby="ft-15-b1">
          <h2 class="ft-15__h" id="ft-15-b1">~/links</h2>
          <ul class="ft-15__list">
            <li><a class="ft-15__link" href="#ft-15-a">projects</a><span class="ft-15__dim">12 repos</span></li>
            <li><a class="ft-15__link" href="#ft-15-a">writing</a><span class="ft-15__dim">38 posts</span></li>
            <li><a class="ft-15__link" href="#ft-15-a">uses</a><span class="ft-15__dim">hardware</span></li>
            <li><a class="ft-15__link" href="#ft-15-a">now</a><span class="ft-15__dim">what I'm on</span></li>
          </ul>
        </section>
        <section class="ft-15__block" aria-labelledby="ft-15-b2">
          <h2 class="ft-15__h" id="ft-15-b2">~/contact</h2>
          <ul class="ft-15__list">
            <li><a class="ft-15__link" href="mailto:hi@jhalvorsen.dev">hi@jhalvorsen.dev</a></li>
            <li><a class="ft-15__link" href="#ft-15-a">github/jhalvorsen</a></li>
            <li><a class="ft-15__link" href="#ft-15-a">@jh@fosstodon.org</a></li>
            <li><a class="ft-15__link" href="#ft-15-a">public key (age)</a></li>
          </ul>
        </section>
        <section class="ft-15__block" aria-labelledby="ft-15-b3">
          <h2 class="ft-15__h" id="ft-15-b3">~/status</h2>
          <dl class="ft-15__dl">
            <div><dt>availability</dt><dd class="ft-15__ok">open · Q4 2026</dd></div>
            <div><dt>location</dt><dd>Bergen, UTC+2</dd></div>
            <div><dt>stack</dt><dd>ts · go · postgres</dd></div>
            <div><dt>reply time</dt><dd>&lt; 2 days</dd></div>
          </dl>
        </section>
      </div>
      <p class="ft-15__rule" aria-hidden="true"></p>
      <p class="ft-15__prompt"><span class="ft-15__sym" aria-hidden="true">&rarr;</span> <span class="ft-15__cmd">echo &quot;thanks for scrolling this far&quot;</span><span class="ft-15__caret" aria-hidden="true"></span></p>
      <div class="ft-15__stamp">
        <span class="ft-15__badge"><span class="ft-15__dot" aria-hidden="true"></span>build ok</span>
        <span class="ft-15__meta">commit <a class="ft-15__link" href="#ft-15-a">a7f31c9</a></span>
        <span class="ft-15__meta">deployed <time datetime="2026-08-05T14:12:00Z">05 Aug 2026, 14:12 UTC</time></span>
        <span class="ft-15__meta">© <time datetime="2026">2026</time> J. Halvorsen · <a class="ft-15__link" href="#ft-15-a">MIT</a></span>
      </div>
    </div>
  </footer>
</section>
.ft-15 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  background: var(--ft-15-bg);
  --ft-15-bg: oklch(0.16 0.014 160);
  --ft-15-foot: oklch(0.13 0.012 160);
  --ft-15-ink: oklch(0.93 0.02 150);
  --ft-15-mut: oklch(0.65 0.02 150);
  --ft-15-dim: oklch(0.5 0.018 150);
  --ft-15-acc: oklch(0.82 0.16 150);
  --ft-15-line: oklch(0.93 0.02 150/.18);
  font-family: ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace;
  color: var(--ft-15-ink);
  -webkit-font-smoothing: antialiased;
}

.ft-15 *,
.ft-15 *::before,
.ft-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ft-15 ul {
  list-style: none;
}

.ft-15__main {
  display: grid;
  align-content: center;
  gap: 14px;
  padding-block: clamp(40px,8vh,92px);
  padding-inline: max(clamp(20px,5vw,44px),calc((100% - 82ch)/2));
}

.ft-15__eyebrow {
  font-size: clamp(11px,1.1vw,12.5px);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ft-15-acc);
}

.ft-15__title {
  font-size: clamp(26px,4.6vw,48px);
  line-height: 1.06;
  letter-spacing: -.03em;
  font-weight: 600;
}

.ft-15__sub {
  font-size: clamp(14px,1.5vw,16.5px);
  line-height: 1.7;
  color: var(--ft-15-mut);
  max-width: 70ch;
  text-wrap: pretty;
}

.ft-15__foot {
  background: var(--ft-15-foot);
  border-top: 1px solid var(--ft-15-line);
  padding-inline: max(clamp(20px,5vw,44px),calc((100% - 82ch)/2));
}

.ft-15__term {
  display: grid;
  gap: 16px;
  padding-block: clamp(26px,4vw,40px);
  max-inline-size: 82ch;
}

.ft-15__rule {
  height: 1px;
  background: repeating-linear-gradient(90deg,currentColor 0 .5ch,transparent .5ch 1ch);
  color: var(--ft-15-dim);
  opacity: .6;
}

.ft-15__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(22ch,1fr));
  gap: clamp(18px,2.5vw,30px);
}

.ft-15__block {
  display: grid;
  align-content: start;
  gap: 6px;
  min-width: 0;
}

.ft-15__h {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ft-15-acc);
}

.ft-15__h::before {
  content: '# ';
  color: var(--ft-15-dim);
}

.ft-15__list {
  display: grid;
  gap: 0;
}

.ft-15__list li {
  display: flex;
  align-items: baseline;
  gap: 1ch;
  min-height: 30px;
}

.ft-15__link {
  font-size: 13.5px;
  color: var(--ft-15-ink);
  text-decoration: none;
  border-bottom: 1px dashed color-mix(in oklch,currentColor 35%,transparent);
  transition: color .18s ease,border-color .18s ease;
}

.ft-15__link:hover {
  color: var(--ft-15-acc);
  border-bottom-style: solid;
  border-bottom-color: currentColor;
}

.ft-15__link:focus-visible {
  outline: 2px solid var(--ft-15-acc);
  outline-offset: 3px;
  border-radius: 3px;
}

.ft-15__dim {
  font-size: 12px;
  color: var(--ft-15-dim);
}

.ft-15__dl {
  display: grid;
  gap: 2px;
}

.ft-15__dl>div {
  display: flex;
  gap: 1ch;
  min-height: 28px;
  font-size: 13px;
  align-items: baseline;
}

.ft-15__dl dt {
  width: 13ch;
  flex: none;
  color: var(--ft-15-dim);
}

.ft-15__dl dt::after {
  content: ':';
}

.ft-15__dl dd {
  color: var(--ft-15-mut);
}

.ft-15__ok {
  color: var(--ft-15-acc) !important;
}

.ft-15__prompt {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1ch;
  font-size: 13.5px;
  line-height: 1.6;
}

.ft-15__sym {
  color: var(--ft-15-acc);
}

.ft-15__cmd {
  color: var(--ft-15-mut);
}

.ft-15__caret {
  display: inline-block;
  width: .62ch;
  height: 1.05em;
  translate: 0 .16em;
  background: var(--ft-15-acc);
  animation: ft-15-blink 1.06s steps(1,end) infinite;
}

@keyframes ft-15-blink {
  50% {
    opacity: 0;
  }
}

.ft-15__stamp {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 2ch;
  padding-block-start: 6px;
  padding-bottom: calc(6px + env(safe-area-inset-bottom,0px));
  font-size: 12px;
  color: var(--ft-15-dim);
}

.ft-15__badge {
  display: inline-flex;
  align-items: center;
  gap: .8ch;
  padding: 5px 10px;
  border-radius: 6px;
  background: color-mix(in oklch,var(--ft-15-acc) 12%,transparent);
  color: var(--ft-15-acc);
  font-size: 11.5px;
}

.ft-15__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: ft-15-pulse 2.6s ease-in-out infinite;
}

@keyframes ft-15-pulse {
  0%,
    100% {
    opacity: 1;
  }

  50% {
    opacity: .3;
  }
}

.ft-15__meta {
  white-space: nowrap;
}

@media (max-width: 520px) {
  .ft-15__meta {
    white-space: normal;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ft-15__caret,
    .ft-15__dot {
    animation: none;
  }

  .ft-15 * {
    transition-duration: .01ms !important;
  }
}
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 Footer Design 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: Monospaced Developer Portfolio Footer
Source: https://codefronts.com/layouts/css-footer-designs/brutalist-mono/

A footer for people who read git log for fun: everything set in a monospace grid measured in ch, an ASCII rule, a blinking block caret, and a build stamp that makes the site feel like a deployed artefact rather than a page. Restrained, fast, and no ASCII-art clichés.
## HTML
```html
<section class="ft-15" aria-label="Monospaced developer portfolio footer demo">
  <main class="ft-15__main">
    <p class="ft-15__eyebrow">ch units · steps() caret · build stamp</p>
    <h1 class="ft-15__title">footer --format=terminal</h1>
    <p class="ft-15__sub">Every column aligns because the layout is measured in characters, not pixels. The caret blinks on a step curve. The build stamp is the part people actually read.</p>
  </main>
  <footer class="ft-15__foot">
    <div class="ft-15__term">
      <p class="ft-15__rule" aria-hidden="true"></p>
      <div class="ft-15__grid">
        <section class="ft-15__block" aria-labelledby="ft-15-b1">
          <h2 class="ft-15__h" id="ft-15-b1">~/links</h2>
          <ul class="ft-15__list">
            <li><a class="ft-15__link" href="#ft-15-a">projects</a><span class="ft-15__dim">12 repos</span></li>
            <li><a class="ft-15__link" href="#ft-15-a">writing</a><span class="ft-15__dim">38 posts</span></li>
            <li><a class="ft-15__link" href="#ft-15-a">uses</a><span class="ft-15__dim">hardware</span></li>
            <li><a class="ft-15__link" href="#ft-15-a">now</a><span class="ft-15__dim">what I'm on</span></li>
          </ul>
        </section>
        <section class="ft-15__block" aria-labelledby="ft-15-b2">
          <h2 class="ft-15__h" id="ft-15-b2">~/contact</h2>
          <ul class="ft-15__list">
            <li><a class="ft-15__link" href="mailto:hi@jhalvorsen.dev">hi@jhalvorsen.dev</a></li>
            <li><a class="ft-15__link" href="#ft-15-a">github/jhalvorsen</a></li>
            <li><a class="ft-15__link" href="#ft-15-a">@jh@fosstodon.org</a></li>
            <li><a class="ft-15__link" href="#ft-15-a">public key (age)</a></li>
          </ul>
        </section>
        <section class="ft-15__block" aria-labelledby="ft-15-b3">
          <h2 class="ft-15__h" id="ft-15-b3">~/status</h2>
          <dl class="ft-15__dl">
            <div><dt>availability</dt><dd class="ft-15__ok">open · Q4 2026</dd></div>
            <div><dt>location</dt><dd>Bergen, UTC+2</dd></div>
            <div><dt>stack</dt><dd>ts · go · postgres</dd></div>
            <div><dt>reply time</dt><dd>&lt; 2 days</dd></div>
          </dl>
        </section>
      </div>
      <p class="ft-15__rule" aria-hidden="true"></p>
      <p class="ft-15__prompt"><span class="ft-15__sym" aria-hidden="true">&rarr;</span> <span class="ft-15__cmd">echo &quot;thanks for scrolling this far&quot;</span><span class="ft-15__caret" aria-hidden="true"></span></p>
      <div class="ft-15__stamp">
        <span class="ft-15__badge"><span class="ft-15__dot" aria-hidden="true"></span>build ok</span>
        <span class="ft-15__meta">commit <a class="ft-15__link" href="#ft-15-a">a7f31c9</a></span>
        <span class="ft-15__meta">deployed <time datetime="2026-08-05T14:12:00Z">05 Aug 2026, 14:12 UTC</time></span>
        <span class="ft-15__meta">© <time datetime="2026">2026</time> J. Halvorsen · <a class="ft-15__link" href="#ft-15-a">MIT</a></span>
      </div>
    </div>
  </footer>
</section>
```
## CSS
```css
.ft-15 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  background: var(--ft-15-bg);
  --ft-15-bg: oklch(0.16 0.014 160);
  --ft-15-foot: oklch(0.13 0.012 160);
  --ft-15-ink: oklch(0.93 0.02 150);
  --ft-15-mut: oklch(0.65 0.02 150);
  --ft-15-dim: oklch(0.5 0.018 150);
  --ft-15-acc: oklch(0.82 0.16 150);
  --ft-15-line: oklch(0.93 0.02 150/.18);
  font-family: ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace;
  color: var(--ft-15-ink);
  -webkit-font-smoothing: antialiased;
}

.ft-15 *,
.ft-15 *::before,
.ft-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ft-15 ul {
  list-style: none;
}

.ft-15__main {
  display: grid;
  align-content: center;
  gap: 14px;
  padding-block: clamp(40px,8vh,92px);
  padding-inline: max(clamp(20px,5vw,44px),calc((100% - 82ch)/2));
}

.ft-15__eyebrow {
  font-size: clamp(11px,1.1vw,12.5px);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ft-15-acc);
}

.ft-15__title {
  font-size: clamp(26px,4.6vw,48px);
  line-height: 1.06;
  letter-spacing: -.03em;
  font-weight: 600;
}

.ft-15__sub {
  font-size: clamp(14px,1.5vw,16.5px);
  line-height: 1.7;
  color: var(--ft-15-mut);
  max-width: 70ch;
  text-wrap: pretty;
}

.ft-15__foot {
  background: var(--ft-15-foot);
  border-top: 1px solid var(--ft-15-line);
  padding-inline: max(clamp(20px,5vw,44px),calc((100% - 82ch)/2));
}

.ft-15__term {
  display: grid;
  gap: 16px;
  padding-block: clamp(26px,4vw,40px);
  max-inline-size: 82ch;
}

.ft-15__rule {
  height: 1px;
  background: repeating-linear-gradient(90deg,currentColor 0 .5ch,transparent .5ch 1ch);
  color: var(--ft-15-dim);
  opacity: .6;
}

.ft-15__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(22ch,1fr));
  gap: clamp(18px,2.5vw,30px);
}

.ft-15__block {
  display: grid;
  align-content: start;
  gap: 6px;
  min-width: 0;
}

.ft-15__h {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ft-15-acc);
}

.ft-15__h::before {
  content: '# ';
  color: var(--ft-15-dim);
}

.ft-15__list {
  display: grid;
  gap: 0;
}

.ft-15__list li {
  display: flex;
  align-items: baseline;
  gap: 1ch;
  min-height: 30px;
}

.ft-15__link {
  font-size: 13.5px;
  color: var(--ft-15-ink);
  text-decoration: none;
  border-bottom: 1px dashed color-mix(in oklch,currentColor 35%,transparent);
  transition: color .18s ease,border-color .18s ease;
}

.ft-15__link:hover {
  color: var(--ft-15-acc);
  border-bottom-style: solid;
  border-bottom-color: currentColor;
}

.ft-15__link:focus-visible {
  outline: 2px solid var(--ft-15-acc);
  outline-offset: 3px;
  border-radius: 3px;
}

.ft-15__dim {
  font-size: 12px;
  color: var(--ft-15-dim);
}

.ft-15__dl {
  display: grid;
  gap: 2px;
}

.ft-15__dl>div {
  display: flex;
  gap: 1ch;
  min-height: 28px;
  font-size: 13px;
  align-items: baseline;
}

.ft-15__dl dt {
  width: 13ch;
  flex: none;
  color: var(--ft-15-dim);
}

.ft-15__dl dt::after {
  content: ':';
}

.ft-15__dl dd {
  color: var(--ft-15-mut);
}

.ft-15__ok {
  color: var(--ft-15-acc) !important;
}

.ft-15__prompt {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 1ch;
  font-size: 13.5px;
  line-height: 1.6;
}

.ft-15__sym {
  color: var(--ft-15-acc);
}

.ft-15__cmd {
  color: var(--ft-15-mut);
}

.ft-15__caret {
  display: inline-block;
  width: .62ch;
  height: 1.05em;
  translate: 0 .16em;
  background: var(--ft-15-acc);
  animation: ft-15-blink 1.06s steps(1,end) infinite;
}

@keyframes ft-15-blink {
  50% {
    opacity: 0;
  }
}

.ft-15__stamp {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 2ch;
  padding-block-start: 6px;
  padding-bottom: calc(6px + env(safe-area-inset-bottom,0px));
  font-size: 12px;
  color: var(--ft-15-dim);
}

.ft-15__badge {
  display: inline-flex;
  align-items: center;
  gap: .8ch;
  padding: 5px 10px;
  border-radius: 6px;
  background: color-mix(in oklch,var(--ft-15-acc) 12%,transparent);
  color: var(--ft-15-acc);
  font-size: 11.5px;
}

.ft-15__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: ft-15-pulse 2.6s ease-in-out infinite;
}

@keyframes ft-15-pulse {
  0%,
    100% {
    opacity: 1;
  }

  50% {
    opacity: .3;
  }
}

.ft-15__meta {
  white-space: nowrap;
}

@media (max-width: 520px) {
  .ft-15__meta {
    white-space: normal;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ft-15__caret,
    .ft-15__dot {
    animation: none;
  }

  .ft-15 * {
    transition-duration: .01ms !important;
  }
}
```

How this works

The layout unit is ch — the width of the digit zero in the current font. In a monospace face that means every character occupies exactly 1ch, so you can build a real terminal grid:

.term{ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
       max-inline-size: 78ch }        /* 78-column terminal */
.key{ inline-size: 12ch }              /* label column, exactly aligned */

Labels line up perfectly without a grid or a table, and the whole block scales with the font size instead of breaking at a fixed pixel width.

The caret is one pseudo-element and a steps() animation — the trick is that a linear blink looks wrong; a caret is on or off, never fading:

@keyframes blink{ 50%{ opacity:0 } }
.caret{ animation: blink 1.06s steps(1, end) infinite }

1.06s is roughly the VT100 blink rate; anything faster reads as an error state.

The horizontal rules are not borders — they are a repeating gradient that draws dashes at exact character positions, so the rule aligns with the text grid:

.rule{ block-size:1px;
  background: repeating-linear-gradient(90deg, currentColor 0 .5ch, transparent .5ch 1ch);
  opacity:.35 }

The status line is the payoff: a live-ish dot, the current commit short SHA, the build time as a <time>, and the runtime. It costs nothing, and it is what makes a developer trust the rest of the site.

Make it yours

  • Terminal width: change the ch value on the container. 72ch and 80ch both feel authentic; over 100ch stops reading as a terminal.
  • Prompt symbol: the ::before content on each command line. Use $, ~, or your shell's actual prompt.
  • Colour scheme: three tokens map to a classic terminal palette. Solarized, Nord and Gruvbox all drop in cleanly since only three hues are used.
  • Real build data: replace the static commit SHA and timestamp with values injected at build time (Vite define, Next.js env, or a generated JSON) so the stamp is genuinely true.

Gotchas — read before shipping

  • ch only guarantees alignment in a truly monospaced font. If the font stack falls back to a proportional face, the grid collapses — always end the stack with monospace.
  • Blinking text under 3Hz is a WCAG 2.2.2 concern; a single caret is fine, a page full of blinking elements is not. Kill it under prefers-reduced-motion.
  • Terminal-green-on-black is often below 4.5:1 for small text. Check contrast — the palette here uses a lighter green precisely for that reason.
  • Don't fake a live "uptime" counter that resets on every page load; developers notice, and it undermines the credibility the aesthetic is buying you.
  • Monospace fonts are wide. Cap the measure at 78ch and let long link labels wrap, or the footer forces horizontal scroll on a phone.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

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

ch units, steps() and repeating gradients are supported everywhere. ui-monospace resolves to SF Mono / Cascadia / Ubuntu Mono on the respective platforms with a plain monospace fallback.

Search CodeFronts

Loading…