Build a CSS Ruler

Mads Stoumann - Sep 27 '21 - - Dev Community

I'm currently working on a project, where an editor can easily edit breakpoints, using a group of color-coded, stacked <input type="range">-controls, and a bit of JavaScript:

noruler

While this works, it's easier to visualize the breakpoints, if combined with a ruler:

breakpoints

At first, I hardcoded a ruler, using pixels — but then I thought: “What if the editor wants to define the breakpoints in em or ch — or some other unit?”


Let's look into how we can create a configurable ruler, using CSS backround-image, a bunch of variables — and only a tiny bit of JavaScript for a visual editor.

Our ruler will have two sets of “ticks” — low and tall (we'll look into the numbers later):

ruler-num

Both sets of ticks have some initial Custom Properties:

.ruler {
  /* Low ticks */
  --ruler1-bdw: 1px;
  --ruler1-c:  #BBB;
  --ruler1-h: 8px;
  --ruler1-space: 5;

  /* Tall ticks */
  --ruler2-bdw: 1px;
  --ruler2-c:  #BBB;
  --ruler2-h: 20px;
  --ruler2-space: 50;
}
Enter fullscreen mode Exit fullscreen mode

With these properties, we can create a dynamic background-image:

.ruler {
  background-image:
    linear-gradient(90deg, var(--ruler1-c) 0 var(--ruler1-bdw), transparent 0),
    linear-gradient(90deg, var(--ruler2-c) 0 var(--ruler2-bdw), transparent 0);
}
Enter fullscreen mode Exit fullscreen mode

However, without background-repeat and background-size, we're not seeing anything that resembles a ruler!

.ruler {
  background-repeat: repeat-x;
  background-size:
    calc(var(--ruler-unit) * var(--ruler1-space)) var(--ruler1-h),
    calc(var(--ruler-unit) * var(--ruler2-space)) var(--ruler2-h);
}
Enter fullscreen mode Exit fullscreen mode

And that's almost it! We just need to add background-attachment: fixed, so the ruler will stay in place, when we're scrolling!


Adding numbers:

For the numbers, we'll create a list:

<ul class="ruler-x">
  <li></li><li></li> <!-- repeat -->
</ul>
Enter fullscreen mode Exit fullscreen mode

Each blank <li></li> will be a number in our ruler. The number will be aligned to the bottom of the tall “ticks”:

.ruler-x {
  color: var(--ruler-num-c);
  counter-reset: d 0;
  display: flex;
  font-size: var(--ruler-num-fz);
  height: var(--ruler2-h);
  inset-block-start: 0;
  inset-inline-start: calc(var(--ruler-unit) * var(--ruler2-space));
  line-height: 1;
  list-style: none;
  margin: 0;
  opacity: var(--ruler-x);
  overflow: hidden;
  padding: 0;
  position: fixed;
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

We'll use a CSS counter to fill out the numbers, based on the --ruler2-space-property:

.ruler-x li {
  align-self: flex-end;
  counter-increment: d var(--ruler2-space);
  flex: 0 0 calc(var(--ruler-unit) * var(--ruler2-space));
}
.ruler-x li::after {
  content: counter(d);
  line-height: 1;
  padding-inline-start: var(--ruler-num-pi);
}
Enter fullscreen mode Exit fullscreen mode

Creating an Editor

Now, let's create a small editor for our ruler.

We'll use a small JavaScript to update the Custom Properties:

app.addEventListener('input', (e) => {
  const input = e.target;
  const value = input.type === 'checkbox' ? (input.checked ? 1 : 0) : input.value;
  document.body.style.setProperty(input.name, value+(input.dataset.suffix||''));
})
Enter fullscreen mode Exit fullscreen mode

In HTML, add <input>s to <form id="app">, using name for the property to set, value for the value (surprise!) and data-suffix to add any suffix to the value:

<input type="range" name="--ruler1-h" value="8" data-suffix="px">
Enter fullscreen mode Exit fullscreen mode

Repeat for all the properties, you want to be editable.


Demo

Here's a Codepen, where I've added a vertical ruler and an editor, using the technique described above (if you're on iOS, open it full-screen to avoid iframe scrolling-issues):


Bonus: right-to-left

If you want to use the y-axis-ruler with rtl, add a small snippet of CSS:

[dir="rtl"] .ruler {
  background-position: 100% 0;
}
Enter fullscreen mode Exit fullscreen mode

Cover-image from pexels.com

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .