jmhobbs

CSS Seven-Segment Display Tutorial

I had an idea for a little project today that needed a seven-segment display. As a first pass I'm going to implement it in all software before seeking out hardware for it.

Knowing I wanted to run my prototype in the browser I looked for a way to show a seven-segment display. I considered images, but then I decided to challenge myself to implement it in CSS only.

I recently read the CSS Hexagon Tutorial by James Tauber and I felt I could apply some of those techniques to get this working.

Step One: A Segment

The obvious first step is to get a single segment displayed.

The basic shape of a segment is actually well defined in all four edges of the box from the hexagon tutorial.

height: 100px;
width: 100px;
border-top: 30px solid #FF3636;
border-bottom: 30px solid #36FF36;
border-left: 30px solid #3636FF;
border-right: 30px solid #FFFF36;

So now I just needed to extract a single edge for my first segment, which proved quite simple.

height: 0;
width: 100px;
border-top: 30px solid #FF3636;
border-left: 30px solid transparent;
border-right: 30px solid transparent;

Step Two: Fill It Out

Iterating from that first segment is easy, just change which borders you need for which segment you require. However, I needed a positioning system. I decided to go with absolute positioning, with a relative wrapper. Traditionally these displays are addressed with each segment being a letter, so I followed that pattern.

The CSS for this is quite similar from segment to segment, it's mostly positioning and picking which border to colorize.

.seven-segment {
  position: relative;
}
.seven-segment .a {
  position: absolute;
  left: 10px;
  height: 0;
  width: 100px;
  border-top: 30px solid #FF3636;
  border-left: 30px solid transparent;
  border-right: 30px solid transparent;
}
.seven-segment .b {
  position: absolute;
  left: 150px;
  top: 10px;
  height: 100px;
  width: 0;
  border-right: 30px solid #FF3636;
  border-top: 30px solid transparent;
  border-bottom: 30px solid transparent;
}
.seven-segment .c {
  position: absolute;
  left: 150px;
  top: 180px;
  height: 100px;
  width: 0;
  border-right: 30px solid #FF3636;
  border-top: 30px solid transparent;
  border-bottom: 30px solid transparent;
}
.seven-segment .d {
  position: absolute;
  top: 320px;
  left: 10px;
  height: 0;
  width: 100px;
  border-bottom: 30px solid #FF3636;
  border-left: 30px solid transparent;
  border-right: 30px solid transparent;
}
.seven-segment .e {
  position: absolute;
  left: 0;
  top: 180px;
  height: 100px;
  width: 0;
  border-left: 30px solid #FF3636;
  border-top: 30px solid transparent;
  border-bottom: 30px solid transparent;
}
.seven-segment .f {
  position: absolute;
  left: 0;
  top: 10px;
  height: 100px;
  width: 0;
  border-left: 30px solid #FF3636;
  border-top: 30px solid transparent;
  border-bottom: 30px solid transparent;
}

Step Three: G Segment

Here's where it gets interesting. Our standard segment shape will not work for the center segment, the G segment, which is beveled on both sides, something we can't do with a single div. First let's split it up and treat it differently for top and bottom.

Now we'll style these using our regular segment markup.

.g .bottom {
height: 0;
width: 100px;
border-top: 30px solid #FF3636;
border-left: 30px solid transparent;
border-right: 30px solid transparent;
}
.g .top {
height: 0;
width: 100px;
border-bottom: 30px solid #FF3636;
border-left: 30px solid transparent;
border-right: 30px solid transparent;
}

It works, sort of. We now have the shape we want, but it's way too thick. It won't fit in the slot we have without looking strange.

Halving the borders and extending the width makes it look better.

This would be a good stopping point, but the angled edges don't seem as long in G, so I tried to fix that.

.seven-segment .g {
  position: absolute;
  top: 160px;
  left: 10px;
}
.seven-segment .g .bottom {
  height: 0;
  width: 130px;
  border-top: 15px solid #FF3636;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
}
.seven-segment .g .top {
  height: 0;
  width: 130px;
  border-bottom: 15px solid #FF3636;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
}

The best solution I came up with was dealing with it at a smaller size, then using a transform to scale it up to where I wanted it. It's not exact, and it's not the same width as the other segments, but the slopes are right, it fits and it feels as close to balanced as I've been able to get.

.seven-segment .g {
  position: absolute;
  top: 162px;
  left: 35px;
  -webkit-transform: scale(1.4);
  -moz-transform: scale(1.4);
  -ms-transform: scale(1.4);
  -o-transform: scale(1.4);
  transform: scale(1.4);
}
.seven-segment .g .bottom {
  height: 0;
  width: 80px;
  border-top: 15px solid #FF3636;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
}
.seven-segment .g .top {
  height: 0;
  width: 80px;
  border-bottom: 15px solid #FF3636;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
}

Conclusion

That was pretty painless! I wish I had a better solution for G, but this one is passable.

You can check out a nifty demo with some JavaScript and on/off states here.

Got some ideas for how to make G better? Please share in the comments!