The Final Product

By the way, if you find and identify the significance of the easter egg I put in there, leave a comment here or on the pen. I'll buy a month's worth of Codepen Pro for the first person to correctly do so. @hsl figured it out on the pen!

The Goal

UPC, or Universal Product Code, is a system for uniquely identifying products. A product will have the same UPC no matter where it is sold, and that UPC will have an accompanying barcode which can be easily scanned and identified by a computer. The most common variant of this barcode-encoding system, UPC-A, creates a one-to-one correlation between a 12-digit UPC and a product code.

As it turns out, using a bit of SassScript to handle the tedious bits, it is possible to compile CSS rules which generate a barcode from simple markup without using images. Below is an example of the final HTML markup. The upc-reset elements are explained further down, and I'm working to remove them from the markup by using pseudo-elements, but they are required for now. I would also like to use self-closing tags for the upc-digit elements, but since they aren't in the xmlns, browsers won't reliably recognize them. I am considering writing an add-on to the namespace.

  <barcode class="upc-a">
  <upc-reset></upc-reset>
  <upc-digit data-val="0"></upc-digit>
  <upc-digit data-val="2"></upc-digit>
  <upc-digit data-val="2"></upc-digit>
  <upc-digit data-val="0"></upc-digit>
  <upc-digit data-val="0"></upc-digit>
  <upc-digit data-val="0"></upc-digit>
  <upc-reset></upc-reset>
  <upc-digit data-val="1"></upc-digit>
  <upc-digit data-val="2"></upc-digit>
  <upc-digit data-val="5"></upc-digit>
  <upc-digit data-val="0"></upc-digit>
  <upc-digit data-val="3"></upc-digit>
  <upc-digit data-val="3"></upc-digit>
  <upc-reset></upc-reset>
</barcode>

How UPC-A Barcode Encoding is Done

A UPC-A barcode consists of 95 columns, which can each be either black or white. Chunks of those columns can represent any of the twelve base-10 digits which form the Universal Product Code. The exact encoding process is as follows.

  1. The first three columns are always black-white-black. This is a sort of "reset" which allows machine readers to perform sanity checks.
  2. There are now six chunks of seven columns each, and each chunk sequentially represents one of the first six digits of the UPC.
  3. In the middle is another reset pattern. This one is five columns long, and is always white-black-white-black-white.
  4. Once again, six chunks of seven columns each represent the remaining six digits of the UPC.
  5. Finally, the pattern ends with a three-column black-white-black reset pattern.

It seems simple enough, but there are a few quirks.

First of all, you've probably noticed that some columns on a barcode descend further than others. The reset patterns always extend all the way down, as do the "chunks" of seven columns which represent the first and last digit. That is because these digits are special. The first digit is known as the "Product Type" digit, and the value of this digit determines what type of product is being marked. For example, a 3 in this digit would indicate that the labelled item is medication.

The last digit (whose columns descend all the way down) is a checksum and is not part of the UPC at all. It is used to detect errors in the reading process and confirm that the UPC is valid. It is calculated by adding the odd digits, multiplying by three, adding the even digits, dividing by 10, taking the remainder, and subtracting that from 10.

If you looks closely at a barcode, you might also notice that the patterns for a certain number will look different based on whether it is on the left half of the barcode or the right half. That is because, on the left half, the patterns will always start with a white column and end with a black column. On the right half, the colors are always inverted. This is just to allow readers to easily separate the chunks.

Encoding each digit into seven columns of black and white stripes doesn't have any special mathematical trick, it's just a matter of matching the digit to one of the ten pre-defined patterns. Use the values below. If you are on the left half of the barcode, a one means black while a zero means white. On the right half, the colors are inverted.

  • 0 = 0001101
  • 1 = 0011001
  • 2 = 0010011
  • 3 = 0111101
  • 4 = 0100011
  • 5 = 0110001
  • 6 = 0101111
  • 7 = 0111011
  • 8 = 0110111
  • 9 = 0001011

How the CSS Works

At the top level, what we've done is assigned widths to each of the children of the barcode element. The first and last upc-reset elements are 3/95th of the total width, the middle upc-reset is 5/95th of the total width, and each upc-digit is 7/95th of the total width. All of the upc-digit elements are 95% of the height, except for the first and last, which are 100%. The upc-reset elements are also 100% of the height. Set them all to position: relative and display: inline-block, and everything is now sitting right where it needs to be. If you haven't included a CSS reset, you'll also need to add margin: 0 to all children of the barcode element. So far, we have the following. This handles only the actual positioning, sizing, and layout.

  $height: 100px;
$width: 1.6 * $height;

barcode.upc-a {
  display: block;
  width: $width;
  height: $height;
  background: #fff;
  border: 0.1 * $width solid #fff; /* give the barcode some breathing room */
  font-size: 0; /* remove spaces between elements */
}

barcode.upc-a * {
  position: relative;
  display: inline-block;
  margin: 0;
}

barcode.upc-a upc-reset {
  width: (5/95) * $width;
  height: 100%;
}

barcode.upc-a upc-digit {
  width: (7/95) * $width;
  height: 95%;
  margin-bottom: 0.05 * $height; /* make them top-aligned */
}

barcode.upc-a upc-reset:first-of-type, 
barcode.upc-a upc-reset:last-of-type {
  width: (3/95) * $height;
}

barcode.upc-a upc-digit:first-of-type, 
barcode.upc-a upc-digit:last-of-type {
  height: 100%;
  margin: 0; /* clear the margin which was making the short ones top-aligned */
}

Note: There is only one thing that is unusual here: thefont-size property on the barcode element itself. There are actually literal text spaces in between each element in the HTML markup since we've spaced out the code and put each item on its own line. We could fix that by putting the child elements all on one line, with no spaces in between them, but we don't want to do that because that makes ugly code. Instead, we'll just drop the text-size to zero so the spaces are no longer rendered.

Great, now we can move on to the real challenge: generating the UPC patterns for the resets and each digit. Let's do the resets first, since they'll always be the same.

The first and last reset is three bars: black, white, black. We could do something fancy with pseudo-elements to make that work, but we'll run into trouble later on when we have seven bars. Instead, let's use a simpler solution: linear gradients. We'll modify the selectors we already created for the first and last reset elements.

  barcode.upc-a upc-reset:first-of-type, 
barcode.upc-a upc-reset:last-of-type {
  width: (3/95) * $height;
  background: linear-gradient(
    to right,
    #000 0%, #000 33.333%,
    #fff 33.333%, #fff 66.667%,
    #000 66.667%, #000 100%
    );
}

Linear gradients are meant to create, well, a gradient. They're meant to fade, so what we're doing is kind of a hack, but it's a pretty elegant one and one that doesn't fly in the face of web standards. Essentially, by reducing the span between the colors to nothing, we've eliminated the fade between white and black. Now, by modifying the less-specific upc-reset selector to do something similar (except with five columns: white, black, white, black, white), we can make the middle reset bar.

  barcode.upc-a upc-reset {
  width: (5/95) * $width;
  height: 100%;
  background: linear-gradient(
    to right,
    #fff 0%, #fff 20%,
    #000 20%, #000 40%,
    #fff 40%, #fff 60%,
    #000 60%, #000 80%,
    #fff 80%, #fff 100%
    );
}

The digits will obviously be a little bit trickier. We're going to create a Sass Mixin, which is sort of like a function that accepts arguments and then returns a CSS property based on those arguments.

Before we can write the mixin, we'll need to create a reference variable that has all of those patterns (see the description of UPC-A above) stored. For this we'll use a nested Sass List. The first item in the list will be a list containing the pattern for the value "1" (the second will contain the pattern for "2", and so on). Since the pattern for "1" is 0001101 (again, see the technical explanation of UPC-A above for this information), we'll store that as (3,5,6). The numbers represent at which point we switch from white to black or black to white within the seven columns of a digit.

  $upc_patterns: (
  (3,5,6), (2,4,6),
  (2,3,5), (1,5,6),
  (1,2,5), (1,3,6),
  (1,2,3), (1,4,5),
  (1,3,4), (3,4,5)
);

Great, now for that mixin which will use those values. It will need to accept two arguments: a value, which will be any digit between 0 and 9, and another representing which side of the barcode it is on (1 for left, 2 for right). The side decides whether we start with black or white.

This code will need to heavily use the nth Sass function, which allows us to access the nth item in a list. We'll use that to fetch the appropriate pattern from the list of patterns, then generate a linear-gradient property using that pattern.

  @mixin upc-pattern($val, $side) {
  $pattern_vals: nth($upc_patterns, $val+1);
  $colors: (#fff, #000);
  /* if this is the right side, invert the colors */
  @if($side == 2) {
    $colors: (#000, #fff);
  }
  background: linear-gradient(
    to right,
    nth($colors,1) 0%,
    nth($colors,1) nth($pattern_vals,1) * 14.2857%,
    nth($colors,2) nth($pattern_vals,1) * 14.2857%,
    nth($colors,2) nth($pattern_vals,2) * 14.2857%,
    nth($colors,1) nth($pattern_vals,2) * 14.2857%,
    nth($colors,1) nth($pattern_vals,3) * 14.2857%,
    nth($colors,2) nth($pattern_vals,3) * 14.2857%,
    nth($colors,2) 100%
  );
}

Note: 14.2857% is 100% divided by 7. This is just the proportional width of each of the seven columns within the element.

A call to @include upc-pattern(0,0); (which should generate the barcode pattern for the digit 0 on the left half) will compile to linear-gradient(to right, #fff 0%, #fff 42.857%, #000 42.857%, #000 71.429%, #fff 71.429%, #fff 85.714%, #000 85.714%, #000 100%);. Whew. It would be a pain in the ass to hand-write all those rules manually. Thank god for Sass Script.

The next step is to create rules that apply the mixin with the appropriate arguments to each upc-digit element based on the value of that element's data-val attribute and whether it is one of the first six digits (on the left half) or one of the last six digits (on the right half). There's really no choice here except to generate a whole bunch of rules with pseudo-selectors that cover every possible combination of these attributes, but we don't have to write each one by hand. Once again, Sass Script comes to the rescue.

We'll use an @for Sass loop to scan through the 10 possible value for data-val (0 through 9), then use the @if Sass selector within to check whether it's one of the first six of its type or not (this will use the :nth-of-type pseudo selector, see Chris Coyier's useful recipes post to really get the most out of this selector).

  @for $i from 0 through 9 {
  barcode.upc-a upc-digit[data-val="#{$i}"] {
    /* left side */
    &:nth-of-type(-n + 6) {
      @include upc-pattern($i,1);
    }
    /* right side */
    &:nth-of-type(n + 7) {
      @include upc-pattern($i,2);
    }
  }
}

Excellent. It is now a barcode. Compare it to this image of the same barcode. You could scan it with a barcode scanning app on your phone if you wanted to. For one last touch, we'll just go ahead and add the digits below the barcode itself using some pseudo-element trickery.

  barcode.upc-a upc-digit::after {
  position: absolute;
  content: attr(data-val);
  font-family: mono;
  font-size: 0.1 * $height;
  bottom: -0.125 * $height;
  margin-left: 0.02 * $width;
}

barcode.upc-a upc-digit:first-of-type::after {
  bottom: 0;
  left: -0.1 * $width;
}
barcode.upc-a upc-digit:last-of-type::after{
  bottom: 0;
  right: -0.085 * $width;
}

Ta-da! Yours should look more or less like the one at the top of the article. I've made some presentation tweaks to mine; Ive increased the size (just modify the $height Sass variable!) and used some CSS for the body.

  html, body {
  height: 100%;
  min-height: 100%;
  overflow: hidden;
  background: linear-gradient(45deg, #333, #222);
}
barcode {
  opacity: 0.8;
  position: relative;
  margin: 0 auto;
  top: 50%;
  transform: translateY(-50%);
  transition: box-shadow 0.5s ease,
    opacity 0.5s ease;
  box-shadow: 5px 5px 10px rgba(0,0,0,0.6);
}
barcode:hover {
  opacity: 1.0;
  box-shadow: 7px 7px 20px rgba(0,0,0,0.8);
}

Note: I put this stuff in the head section using the Pen Settings dialogue, so that I can include my Barcode Pen CSS file in the future without any interference.