DEV Community

Cover image for Piano Chords in CSS
Mads Stoumann
Mads Stoumann

Posted on

Piano Chords in CSS

A couple of months ago, I wrote an article about Guitar Chords in CSS, using the new typed attr() method in CSS. I didn't plan to write a follow-up, but then I saw these beautiful posters and wanted to do something similar. It turned out to be way more complicated than guitar chords!


Markup

The markup consists of a single custom element, with a keys-attribute:

<piano-chord keys="14">
</piano-chord>
Enter fullscreen mode Exit fullscreen mode

keys indicates how many white keys we want to show on our piano. For now, that's all we need; all the heavy lifting is done in CSS.


CSS

The first thing we do in CSS is grab the keys attribute:

piano-chord {
  --_keys: attr(keys type(<number>), 8);
}
Enter fullscreen mode Exit fullscreen mode

Next, we need a private const to indicate how many white keys are in a regular octave:

--_octave-keys: 7;

OK, so now we can already draw the white keys, which will be simple lines at calculated intervals:

background: linear-gradient(90deg, 
var(--piano-chord-black-key) 
var(--piano-chord-key-gap), 
#0000 0 var(--piano-chord-key-gap));

border-block:
  var(--piano-chord-key-gap)
  solid var(--piano-chord-black-key);
Enter fullscreen mode Exit fullscreen mode

I've added a few properties to set the line color and width. We also need to set the background-size:

background-size: calc(100% / var(--_keys) - (var(--piano-chord-key-gap) / var(--_keys))) 100%;
Enter fullscreen mode Exit fullscreen mode

This gives us:

White piano keys


Next, we need to calculate the octave width, as the five black keys in a regular octave will repeat in the next octave, creating the distinct pattern we know from a piano.

For the next calculations, we need a few more private properties:

Octave width

--_ow: calc(100% / (var(--_keys) / var(--_octave-keys)));
Enter fullscreen mode Exit fullscreen mode

Black key ratio

The black key is always 60% of the width of a white key, but because we change the octave width dynamically, we need another calculation to keep this ratio:

--_r: calc(var(--_keys) / var(--_octave-keys) * 0.6);
Enter fullscreen mode Exit fullscreen mode

White key width:

--_wkw: calc(100% / var(--_keys));
Enter fullscreen mode Exit fullscreen mode

Black key width

--_bkw: calc(var(--_wkw) * var(--_r));
Enter fullscreen mode Exit fullscreen mode

Phew! And we're not done yet! You might recall that the black key is 60% of a white key and placed exactly in the middle between two white keys. Half of 60% is 30%, so we need to place the first black key (between C and D on a piano) at 70% of the first white key (C):

--_off1: calc(0.7 / var(--_octave-keys) * 100%);
Enter fullscreen mode Exit fullscreen mode

To skip forward: The next ones are placed at 1.7, 3.7, 4.7, and 5.7. Now, we need to add five extra gradients to our background, one for each black key in an octave.

I'll just show the code for the first here:

linear-gradient(90deg,
  #0000 0 var(--_off1),
  var(--piano-chord-black-key) var(--_off1)
  calc(var(--_off1) + var(--_bkw)),
  #0000 0))
Enter fullscreen mode Exit fullscreen mode

Thankfully, the background-size is simple this time: var(--_ow) 60%

Let's see what we've got:

Piano Chord Initial

Cool! Let's change keys to keys="21":

Piano with 21 white keys

Isn't that cool? A CSS-only, attribute-controlled piano pattern?


Adding chord indicators

Now, let's add chords. For that, we need a new custom element, <piano-key>:

<piano-chord aria-label="C" keys="14">
  <piano-key note="C"></piano-key>
  <piano-key note="E"></piano-key>
  <piano-key note="G"></piano-key>
 </piano-chord>
Enter fullscreen mode Exit fullscreen mode

That wraps up what we need to do in HTML. Let's go back to CSS. First, we set a piano key to be a grid:

piano-key { display: grid; }
Enter fullscreen mode Exit fullscreen mode

Next, we need to set at which column location the key is:

piano-key[note="C"] {  grid-column: 1; }
piano-key[note="D"] {  grid-column: 2; }
/* etc. */
Enter fullscreen mode Exit fullscreen mode

Next, we add a pseudo-element showing a small dot:

piano-key::after {
  background: var(--_bg, #000);
  border-radius: 50%;
  content: "";
  display: block;
  height: 10px;
  margin-block: 8px;
  place-self: end center;
  width: 10px;
}
Enter fullscreen mode Exit fullscreen mode

This renders:

C major

... and that's almost it! We need to handle sharp and flat notes:

piano-key[note*="#"] {
  translate: 50% -40%;
}
piano-key[note*="b"] {
  translate: -50% -40%;
}
Enter fullscreen mode Exit fullscreen mode

And to make them look just as cool as in the posters I mentioned in the beginning, let's wrap the piano in a wrapper with a dusty blue color and a retro SVG filter (see the final demo for the code!):

C major dusty blue

And that's it! There is, of course, much more to it, but I've covered most of the basics. Here's a CodePen with a bunch of chords:

Top comments (5)

Collapse
 
david_thomas profile image
David Thomas

👍

Collapse
 
madsstoumann profile image
Mads Stoumann

🎹🕶️

Collapse
 
saumendra_swain_82da5a373 profile image
Saumendra Swain

thats is something i got amazed Mads, since many years i was not aware that css gone that far. a great article with insightful demonstration

Collapse
 
madsstoumann profile image
Mads Stoumann

Thank you!

Collapse
 
abhiwantechnology profile image
Abhiwan Technology

wow, this is very intresting, nowadays most of the game development companies in India were using such types of codes while developing games for specific audiences.