Build a CSS Ruler
Tags: css, html, javascript, webdev
Published: 2021-09-27 10:37:59

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8p1qp07nrt67d3v1ew52.png)

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

![breakpoints](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qkxqo8wj79cqbbutkwe.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tkldpe7xtu17g55lmvy7.png) 

Both sets of ticks have some initial *Custom Properties:*

```css
.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;
}
```

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

```css
.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);
}
```

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

```css
.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);
}
```

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:

```html
<ul class="ruler-x">
  <li></li><li></li> <!-- repeat -->
</ul>
```

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

```css
.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%;
}
```

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

```css
.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);
}
```

---

## Creating an Editor

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

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

```js
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||''));
})
```

---

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:

```html
<input type="range" name="--ruler1-h" value="8" data-suffix="px">
```

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

{% codepen https://codepen.io/stoumann/pen/oNwQyxd %} 

---

## Bonus: right-to-left

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

```css
[dir="rtl"] .ruler {
  background-position: 100% 0;
}
```

*Cover-image from pexels.com*