Guitar Chords in CSS
Mads Stoumann

Mads Stoumann @madsstoumann

About: I'm a tech director, web developer, graphic designer, musician, blogger, comicbook-geek, LEGO-collector, food lover … as well as husband and father!

Location:
Copenhagen, Denmark
Joined:
Nov 16, 2020

Guitar Chords in CSS

Publish Date: May 7
138 30

In my previous article, I looked into the new, improved attr() method in CSS. I was over the moon (pun intended). This time, I’ll continue looking into the attr() method, showing how we can make easily readable components — at least for guitarists — like this:

<fret-board frets="4" strings="6" chord="C Major">
  <string-note string="6" mute></string-note>
  <string-note string="5" fret="3" finger="3"></string-note>
  <string-note string="4" fret="2" finger="2"></string-note>
  <string-note string="3" open></string-note>
  <string-note string="2" fret="1" finger="1"></string-note>
  <string-note string="1" open></string-note>
</fret-board>
Enter fullscreen mode Exit fullscreen mode

... become this — with no JavaScript at all:

fret-board component showing a C Major chord

Let’s get started!

Basic grid

First, we need a basic grid. How many cells and rows depend on the frets and strings attributes we defined in the HTML — so let’s grab those as <number> and set two custom properties:

--_frets: attr(frets type(<number>), 4);
--_strings: attr(strings type(<number>), 6);
Enter fullscreen mode Exit fullscreen mode

In CSS, we’ll double the number of strings, so we can place notes on the strings — one grid-unit to the left of the string itself. We also want top and bottom rows for chord-name and open/mute indicators, so our grid looks like this:

fret-board {
  grid-template-columns: repeat(calc(var(--_strings) * 2), 1fr);
  grid-template-rows:
    var(--fret-board-top-row-h, 12%)
    repeat(calc(var(--_frets)), 1fr)
    var(--fret-board-bottom-row-h, 15%);
}
Enter fullscreen mode Exit fullscreen mode

We now have:

Basic grid

Frets and strings

The frets and strings are added to a pseudo-element as two linear gradients:

fret-board {
  --fret-board-fret-c: light-dark(#000, #FFF);
  --fret-board-fret-w: clamp(0.0625rem, 0.03125rem + 0.5cqi, 0.5rem);
  --fret-board-string-c: light-dark(#0008, #FFF8);
  --fret-board-string-w: clamp(0.0625rem, 0.03125rem + 0.5cqi, 0.125rem);

  background-image:
    /* Vertical strings */
    linear-gradient(
      90deg, 
      var(--fret-board-string-c) var(--fret-board-string-w), 
      transparent 0 var(--fret-board-string-w)
    ),
    /* Horizontal frets */
    linear-gradient(
      180deg,  
      var(--fret-board-fret-c) var(--fret-board-fret-w), 
      transparent 0 var(--fret-board-fret-w)
    );

  background-position: 
    0 var(--fret-board-fret-w), 
    0 0;

  background-repeat: 
    repeat-x, 
    repeat-y;

  background-size:
    /* Width and height for strings */
    calc(100% / (var(--_strings) - 1) - (var(--fret-board-string-w) / var(--_strings))) 
      calc(100% - (2 * var(--fret-board-fret-w))),
    /* Width and height for frets */
    100% 
      calc(100% / var(--_frets) - (var(--fret-board-fret-w) / var(--_frets)));
}
Enter fullscreen mode Exit fullscreen mode

OK, that was a handful — but we basically created horizontal lines for frets from top to bottom, and vertical lines for the strings running across the board.

Added frets and strings

Adding notes and fingers

Let’s add some notes — but first, let’s grab those attributes:

--barre: attr(barre type(<number>), 1);
--fret:  attr(fret type(<number>), 0);
--string:  attr(string type(<number>), 0);
Enter fullscreen mode Exit fullscreen mode

We’ll get to --barre soon, but for now, we place the note using this formula:

string-note {
  grid-column: calc((var(--_strings) * 2) - (var(--string) * 2 - 1)) / span calc(var(--barre) * 2);  
  grid-row: calc(var(--fret) + 1);
}
Enter fullscreen mode Exit fullscreen mode

Let’s break this down:

  1. Grid Column Positioning:

    • (var(--_strings) * 2) - This starts at the rightmost side of our grid
    • (var(--string) * 2 - 1) - This calculates how far to move from the right
    • For example, with 6 strings:
      • String 1 (highest) goes at position 12 - (1*2-1) = 11
      • String 6 (lowest) goes at position 12 - (6*2-1) = 1
  2. Span Calculation:

    • span calc(var(--barre) * 2) - For regular notes, spans 2 columns
    • For barre chords, spans more columns depending on how many strings are covered
  3. Grid Row Formula:

    • calc(var(--fret) + 1) - The +1 accounts for the header row
    • Open strings (fret = 0) go in row 1, first fret in row 2, etc.

This gets us:

Notes and fingers

Displaying finger numbers

For good tablature, we need to show which finger to use for each note. We get this from the finger attribute in our HTML and display it using a pseudo-element:

string-note::after {
  color: var(--string-note-c, light-dark(#FFF, #222));
  content: attr(finger);
  font-size: var(--string-note-fs, 7cqi);
  font-weight: var(--string-note-fw, 500);
  text-box: cap alphabetic;
}
Enter fullscreen mode Exit fullscreen mode

Note: text-box: cap alphabetic is a modern CSS feature that trims spacing created by line-height. The light-dark() function automatically adjusts text color for light or dark mode.

Muted and open strings

Guitar chords often involve strings that aren’t pressed down but are either played open or muted (not played):

<string-note string="6" mute></string-note>    <!-- Muted string -->
<string-note string="3" open></string-note>    <!-- Open string -->
Enter fullscreen mode Exit fullscreen mode

For the visual representation of string states:

  • For muted strings, we create an X symbol using Temani Atif’s cross shape with border-image and a 45° rotation
  • For open strings, we use a CSS mask with a radial gradient to create a hollow circle effect
string-note[mute] {
  border-image: conic-gradient(var(--fret-board-bg) 0 0) 50%/calc(50% - 0.25cqi);
  rotate: 45deg;
}
string-note[open] {
  border-radius: 50%;
  mask: radial-gradient(circle farthest-side at center, 
    transparent calc(100% - 1cqi), 
    #000 calc(100% - 1cqi + 1px));
}
Enter fullscreen mode Exit fullscreen mode

Barre Chords

A "barre chord" is one where you hold down multiple strings with one finger. Let’s add the barre attribute in HTML:

<string-note string="6" fret="1" barre="6" finger="1"></string-note>
Enter fullscreen mode Exit fullscreen mode

We already covered the calculations above, but here’s how it looks visually:

Barre Chord

Fret Numbers

Sometimes, a chord does not start on the first fret, and we need to indicate a fret number. For this, we use an ordered list, <ol>, where we set the value attribute on the first item:

<ol><li value="7"></li></ol>
Enter fullscreen mode Exit fullscreen mode

You can inspect the styles in the final demo, but it looks like this:

Fret Number

Other instruments with frets and strings

Now, the great thing about controlling the CSS from a few attributes is how easy we can make it work for other instruments. Here are some examples:

Ukulele — 4 strings:

Ukulele

Banjo — 5 strings:

Banjo


Demo

Here’s a demo. Please note that it only works in Chrome (for now):

I hope you enjoyed this article as much as I did writing it — now, I’ll grab my guitar and play some of these chords.

Comments 30 total

  • artydev
    artydevMay 7, 2025

    Awesome :-)

  • Ramon Polidura
    Ramon PoliduraMay 8, 2025

    This is great! are you expanding it? playing the sound, making it interactive?

    • Mads Stoumann
      Mads StoumannMay 8, 2025

      Thanks! Yeah, might do that — first, I want to create a wrapper component that'll show all versions of a given chord.

  • Nathan Tarbert
    Nathan TarbertMay 8, 2025

    Pretty cool way to lay out guitar chords in CSS like that, no JS needed. Makes me wonder if stuff like this will push more music tools to be just pure CSS/html eventually or if there'll always be a spot for heavier frameworks?

  • Filipe Santiago Santos
    Filipe Santiago SantosMay 8, 2025

    Incredible, man! I never thought of representing chords like this

    • Mads Stoumann
      Mads StoumannMay 9, 2025

      I think it’s a common way to display guitar chords; I have an old book of guitar chords, represented like this. If you add direction: rtl; it works for left-handed guitarists as well!

  • YCM Jason
    YCM JasonMay 12, 2025

    This is lovely!

  • Jason
    JasonMay 12, 2025

    Absolutely love this! Feels like the perfect blend of dev brain and musician brain. I’ve been working on a few music-related tools myself and this hit that sweet spot of simplicity and usefulness. Really appreciate how readable the markup is too. You made it easy to extend or drop into other projects without a bunch of dependencies. Total win. Thanks for sharing this!

    • Mads Stoumann
      Mads StoumannMay 12, 2025

      Thank you – really hope someone will use it in their own projects!

  • chriskuku
    chriskukuMay 13, 2025

    Thought the demo would play (audible) but it doesn't or at least I don't know how to play.
    (BigSur 11.7.10, Google Chrome

    • Mads Stoumann
      Mads StoumannMay 13, 2025

      No, it only teaches finger-positions for chords. You have to pick up a guitar and play yourself ;-)

      • chriskuku
        chriskukuMay 13, 2025

        Ah, I see, thanks. So the remark that it is running under Chrome has been misleading me to think it would produce sound, but that's hardly to work when it's CSS only :)
        A for rendering it seems to work under Firefox either.

        • Mads Stoumann
          Mads StoumannMay 13, 2025

          Exactly! Chrome is the only browser that currently supports the updated attr()-method in CSS, hence the "only in Chrome" comment. I wrote a small polyfill for other browsers.

  • Joao Carmo Pereira
    Joao Carmo PereiraMay 13, 2025

    Awesome stuff :) Very well designed :)

    On guitar change A Major and A7 chords to start with 123 and 13. B7 to 2134 :P

  • Julian Knight
    Julian KnightMay 13, 2025

    Would love to see this as a published web component. Would also love for it to play the chord. Some other musical ideas: Full tablature song display; Full sheet music display (with optional guitar chords of course!).
    Great work.

  • Joel Dodson
    Joel DodsonMay 13, 2025

    As a backend dev learning about the front end, this is very cool. As a totally blind person trying to learn to play the guitar, this is frustrating. Is there any way to make this work with a screen reader? There is so little accessible content out there already. I'd love to explore how this might be made accessible.

    standardguitar.com/accessible-guit...
    is a great resource, but not something I could embed in a HTML doc to be used by sighted and blind people alike.

    • Mads Stoumann
      Mads StoumannMay 13, 2025

      Interesting, thank you for pointing that out! The markup consists of two custom elements, fret-board and string-note. Fret-board have “frets” and “strings” attributes, while string-note have “string”, “fret” and “finger” attributes — but while I find that intuitive and easy-to-grasp, I must admit I didn’t check how it’s presented to a screen-reader. I’ll look into that.

      • Joel Dodson
        Joel DodsonMay 13, 2025

        thanks Mads. I was thinking of an aria-label in the fret-board element.
        Alternatively, I was thinking of doing something analogous to your CSS using SVG which would be included with an img element which honors the alt attribute.
        Ultimately, I'd like something that default announces the chord (e.g., C major) but only enumerates the strings and frets when a key is pressed. A details/summary comboe could work but a screen reader by default would say "button collapsed"... too verbose if you already know the chord and just want to learn a song.
        probably unlikely to find a solution that works across all browsers and all screen readers :)

        • Mads Stoumann
          Mads StoumannMay 14, 2025

          Hi Joel,
          I've added a details/summary section with chord-name and chord-info.
          In Chrome DevTools, I wasn't able to see the expanded content in the A11y Tree unless I pre-loaded it with "open", but that might simply be an issue with refresh. Otherwise, let me know — then I can add the chord-info to an aria-label on the main fret-board element.

          You can test it here:
          browser.style/ui/fret-board/

          Or install it from here:
          npm i @browser.style/fret-board

          Best wishes,
          Mads

          • Joel Dodson
            Joel DodsonMay 15, 2025

            Hi Mads,
            that's awesome!
            Using single letter navigation in NVDA (screen reader I use), I could type 'f' and go to the next form element. that takes me to the next "button" (how details element is considered( which would tell me the chord name. I press space (or enter) and that expands the details and I hear the fingering for the chord. Perfect.
            I don't know if I'd add aria-label. First, I don't know if it would work as, I think, by default the custom elements do not get focus. I don't know how aria-label would work in that case.
            I really am in the learning process re front end and accessiblity. I developed infrastructure for telecom and API platforms before losing my sight. I knew nothing about accessibility and very little about front end dev. I was thrust into that world.
            Anyway, the point I'm taking a long time to not make clearly is I'm high level familiar with accessibility trees and dynamic changes to html docs in the browser plus how CSS fits into all that.
            I really appreciate you taking the time to try these changes. I'm very motivated now to go back through your post and really understand how this all works.
            I'm developing a python package to server side render html docs of arbitrary complexity using classes to represent elements. The idea is accessibility will be enforced within the classes, to the degree possible. As part of that project, I have builders which are higher level with interfaces to encapsulate classes and attributes needed to create, for example, an accessible HTML table.
            I'd love to add a chord library based on your work to the builders.

            • Mads Stoumann
              Mads StoumannMay 15, 2025

              Happy to hear it worked - and thank you for sharing all this. Reach out if you encounter any issues.

  • Mike Molony
    Mike MolonyMay 18, 2025
    box-shadow: 0 calc(0px - var(--fred-board-fret-bbsw, 1.5cqi)) 0 0 var(--fret-board-fret-c);
    
    Enter fullscreen mode Exit fullscreen mode

    Who's Fred? Haha. should be 'fret' but makes little difference. Great job!

Add comment