Not Quite Horizontal Rules

Angular Rules

I really wanted to call this post "Angular Rules" but it's not at all about AngularJS, and I generally dislike clickbait. Rather than mislead people on what this is about, let me make myself clear: This post is about styling horizontal rules to be angular such that they slope at a nice angle.


A project I'm working on has need of a separator that rises at an angle. The effect can be achieved using an <hr> element and giving it a rotation.

Unfortunately, to make the rotation work, a sizable margin needs to be applied above and below to prevent other elements from overlapping the rotated <hr>.

At particularly large screen sizes, even the large margin might not be enough. At particularly small screen sizes, the margin looks too big.

Not being content to leave well enough alone, I decided to come up with a different approach.

My first attempt was to use a linear gradient. I figured it'd be pretty straightforward to draw a thin angled line by treating it as a gradient:

  • 0 - ~50% could be transparent
  • ~50% - ~50% could be grey
  • ~50% - 100% could be transparent

Given that the normal <hr> styles are about 2px thick, I decided to use ±1px. To give the line a slant I set the direction to to bottom right, this gave me a linear gradient of:

linear-gradient(
  to bottom right,
  transparent 0%,
  transparent calc(50% - 1px),
  #888 calc(50% - 1px),
  #888 calc(50% + 1px),
  transparent calc(50% + 1px),
  transparent 100%
);

Unfortunately, the gradient had awful anti-aliasing issues due to the abrupt transition from transparent to opaque. Given that anti-aliasing is just a form of gradient, I decided to make the transition much smoother by transitioning in from 50% - 1px to 50% and transitioning out from 50% to 50% + 1px.

The gradient helped ease the anti-aliasing issue, but the angled rule still had responsive issues. Style guidelines dictated that the angle must remain a specific value at all screen sizes, and as the screen gets narrower, the angle gets steeper.

The fix for this is pretty straight forward. linear-gradient can take a specific angle in place of the side-or-corner parameter. Swapping to bottom right with -5deg makes the angle stay at -5° regardless of screen size.

Of course, this brings with it new responsive problems. In wide regions the rule is padded horizontally. In narrow regions the rule becomes padded vertically.

The inconsistent spacing is problematic. It would be nice to have the rule maintain its aspect ratio as the container resizes. Fortunately, I already know about a technique for maintaining aspect ratio. Because I only need to size the element, I can explicitly set the padding-top or padding-bottom rather than the height. Now the problem is determining what size to set for the padding-top.

Reviewing my previous blog post about aspect ratio I noted that the percentage used for padding-top was the simple ratio of:

$$\frac{\text{height}}{\text{width}}$$

Of course, I know neither the height nor the width that the not-so-horizontal rule will be displayed at. All I have is the angle…

…and a pretty good grasp on trigonometry.

If you haven't touched trig since high school, you can be forgiven for not remembering SOH-CAH-TOA. The short version is:

$$\text{the sine of an angle} = \frac{\text{length of opposite side of right triangle}}{\text{length of hypotenuse}}$$ $$\text{the cosine of an angle} = \frac{\text{length of adjacent side of right triangle}}{\text{length of hypotenuse}}$$ $$\text{the tangent of an angle} = \frac{\text{length of opposite side of right triangle}}{\text{length of adjacent side}}$$

With that information and looking at a diagram

triangle with edges labelled

It's not too difficult to see that

$$\tan{\theta} = \frac{\text{opposite}}{\text{adjacent}} = \frac{\text{height}}{\text{width}} = \text{padding-top}$$

Super convenient.

$$\tan{5°} = 0.08748866… = 8.75\%$$

With that we now have a nice responsive angled rule to separate content.