DEV Community

Cover image for Guitar Chords in CSS
Mads Stoumann
Mads Stoumann

Posted on

Guitar Chords in CSS

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.

Top comments (30)

Collapse
 
joeldodson profile image
Joel Dodson

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.

Collapse
 
madsstoumann profile image
Mads Stoumann

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.

Collapse
 
joeldodson profile image
Joel Dodson

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 :)

Thread Thread
 
madsstoumann profile image
Mads Stoumann

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

Thread Thread
 
joeldodson profile image
Joel Dodson

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.

Thread Thread
 
madsstoumann profile image
Mads Stoumann

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

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

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?

Collapse
 
madsstoumann profile image
Mads Stoumann

Thanks! Any specific tools you have in mind?
I did a CSS-grid piano:
codepen.io/stoumann/pen/ExpNrLR
Used in a Chord Visualizer:
codepen.io/stoumann/pen/dyjjWqJ

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Awesome!
This just really spoke to me as a guitar player :)

Collapse
 
bennysutton profile image
BennySutton

I've done some similar music/dev - I used SVG for piano bennysutton.com/chords/the-1-5-4-6... - probably would have used your css approach had I seen it first. With relation to guitar chords the real problem is how to calculate all the different inversions and finger positions bennysutton.com/chords/chord-scales I created a json file that I would be happy to share with you

Collapse
 
totallyinformation profile image
Julian Knight

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.

Collapse
 
madsstoumann profile image
Mads Stoumann

I need more time 😁

Collapse
 
epochdad profile image
Jason

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!

Collapse
 
madsstoumann profile image
Mads Stoumann

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

Collapse
 
artydev profile image
artydev

Awesome :-)

Collapse
 
madsstoumann profile image
Mads Stoumann

Thank you!

Collapse
 
raypoly profile image
Ramon Polidura

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

Collapse
 
madsstoumann profile image
Mads Stoumann

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

Collapse
 
santiaghou profile image
Filipe Santiago Santos

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

Collapse
 
madsstoumann profile image
Mads Stoumann

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!

Collapse
 
ycmjason profile image
YCM Jason

This is lovely!

Collapse
 
madsstoumann profile image
Mads Stoumann

Thanks!

Collapse
 
chriskuku profile image
chriskuku

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

Collapse
 
madsstoumann profile image
Mads Stoumann

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

Collapse
 
chriskuku profile image
chriskuku

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.

Thread Thread
 
madsstoumann profile image
Mads Stoumann

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.

Collapse
 
mike_molony_103667331a540 profile image
Mike Molony
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!

Collapse
 
madsstoumann profile image
Mads Stoumann

Haha, well spotted! Thx