Systematically add different CSS border to each A, even if they are in children divs

Viewed 63

I have a predicament. I have a menu, that is structured the following:

<ul class="main-menu">
  <li class="menu-item" <a href="">Menu item 1</a>
  </li>
  <ul class="sub-menu">
    <li class="menu-item"><a href="">Submenu item 1</a></li>
    <li class="menu-item has-children"><a href="">Submenu item 2</a></li>
    <ul class="sub-menu">
      <li class="menu-item"><a href="">Sub-sub menu item 1</a></li>
      <li class="menu-item"><a href="">Sub-sub menu item 2</a></li>
    </ul>
    <li class="menu-item"><a href="">Submenu 3</a></li>
    <li class="menu-item has-children"><a href="">Submenu 4</a></li>
    <ul class="sub-menu">
      <li class="menu-item"><a href="">Sub-sub menu item 3</a></li>
      <li class="menu-item"><a href="">Sub-sub menu item 4</a></li>
    </ul>
  </ul>
  <li class="menu-item" <a href="">Menu item 2</a>
  </li>
</ul>

I need to systematically assign a new border colour to EACH "a" tag in the list, to create a rainbow border effect (so first "A" found (Menu item 1) has blue border, second "a" (Submenu item 1) has red, third (Sub-sub menu item 1) has pink, fourth (Sub-sub menu item 2) has green, etc).

The problem is, because there are sub-menu's inside sub-menu's, I cannot use the :nth-child psuedo, because it doesn't include children in the counts. Like this for example:

    ul li a:nth-child(1) {
       border: 1px solid blue;
    }

    ul li a:nth-child(2) {
       border: 1px solid pink;
    }

Is there a way to systematically go through each "a" inside the main-menu class, and add different css to each item? In a future proof way, so if the menu changes (e.g. second top-level menu item is added, or an additional submenu item is added), then colours still follow the same order?

Ideally via CSS, but open to jQuery ideas also!

2 Answers

If we consider the fact that we always have one line per link and each line will contain a link and the height of a line is known (or we can fix it) then here is a CSS only idea:

ul.main-menu {
   --l:1.5em; /* our line-height */

   line-height:var(--l);
   position:relative; /* relative to main element */
   z-index:0;
}

ul.main-menu a {
  clip-path:inset(0); /* clip everything outside the link */
  color:#fff;
  padding:0 5px;
  display:inline-block;
}

ul.main-menu a::before {
  content:"";
  position:absolute;
  z-index:-1;
  top:0;
  left:0;
  right:0;
  bottom:0;
  /* your color array below */
  background:
    repeating-linear-gradient(0deg,
       red    0 calc(1*var(--l)),
       green  0 calc(2*var(--l)),
       blue   0 calc(3*var(--l)),
       purple 0 calc(4*var(--l)),
       pink   0 calc(5*var(--l)));
}
<ul class="main-menu">
  <li class="menu-item"> <a href="">Menu item 1</a></li>
  <li>
    <ul class="sub-menu">
      <li class="menu-item"><a href="">Submenu item 1</a></li>
      <li class="menu-item has-children"><a href="">Submenu item 2</a></li>
      <li>
        <ul class="sub-menu">
          <li class="menu-item"><a href="">Sub-sub menu item 1</a></li>
          <li class="menu-item"><a href="">Sub-sub menu item 2</a></li>
        </ul>
      </li>
      <li class="menu-item"><a href="">Submenu 3</a></li>
      <li class="menu-item has-children"><a href="">Submenu 4</a></li>
      <li>
        <ul class="sub-menu">
          <li class="menu-item"><a href="">Sub-sub menu item 3</a></li>
          <li class="menu-item"><a href="">Sub-sub menu item 4</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="menu-item"> <a href="">Menu item 2</a></li>
</ul>

To have border, we need an extra wrapper:

ul.main-menu {
   --l:1.5em; /* our line-height */

   line-height:var(--l);
   position:relative;
  z-index:0;
}

ul.main-menu a {
  clip-path:inset(0);
  display:inline-block;
}
ul.main-menu a > span {
  display:block;
  padding:0 4px;
  background:linear-gradient(#fff 0 0) center/calc(100% - 6px) calc(100% - 6px) no-repeat;
}

ul.main-menu a::before {
  content:"";
  position:absolute;
  z-index:-1;
  top:0;
  left:0;
  right:0;
  bottom:0;
  background:
    repeating-linear-gradient(0deg,
       red    0 calc(1*var(--l)),
       green  0 calc(2*var(--l)),
       blue   0 calc(3*var(--l)),
       purple 0 calc(4*var(--l)),
       pink   0 calc(5*var(--l)));
}
<ul class="main-menu">
  <li class="menu-item"> <a href=""><span>Menu item 1</span></a></li>
  <li>
    <ul class="sub-menu">
      <li class="menu-item"><a href=""><span>Submenu item 1</span></a></li>
      <li class="menu-item has-children"><a href=""><span>Submenu item 2</span></a></li>
      <li>
        <ul class="sub-menu">
          <li class="menu-item"><a href=""><span>Sub-sub menu item 1</span></a></li>
          <li class="menu-item"><a href=""><span>Sub-sub menu item 2</span></a></li>
        </ul>
      </li>
      <li class="menu-item"><a href=""><span>Submenu 3</span></a></li>
      <li class="menu-item has-children"><a href=""><span>Submenu 4</span></a></li>
      <li>
        <ul class="sub-menu">
          <li class="menu-item"><a href=""><span>Sub-sub menu item 3</span></a></li>
          <li class="menu-item"><a href=""><span>Sub-sub menu item 4</span></a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="menu-item"> <a href=""><span>Menu item 2</span></a></li>
</ul>

Of course, the above rely on a lot of fixed value so you may have to adjust a lot of things for your real code:

This is, to the best of my knowledge, not currently possible with just CSS. While CSS can accurately select all the <a> elements within the list with the selector ul.main-menu a, and it's possible that we could increment a counter with CSS, and even show that counter (in 2021 that's been possible for quite some time):

ul.main-menu {
  counter-reset: anchorElementIndex;
}

ul.main-menu a::before {
  counter-increment: anchorElementIndex;
  content: " (" counter(anchorElementIndex) ") ";
}
<ul class="main-menu">
  <li class="menu-item"><a href="#">Menu item 1</a>
  </li>
  <ul class="sub-menu">
    <li class="menu-item"><a href="#">Submenu item 1</a></li>
    <li class="menu-item has-children"><a href="#">Submenu item 2</a></li>
    <ul class="sub-menu">
      <li class="menu-item"><a href="#">Sub-sub menu item 1</a></li>
      <li class="menu-item"><a href="#">Sub-sub menu item 2</a></li>
    </ul>
    <li class="menu-item"><a href="#">Submenu 3</a></li>
    <li class="menu-item has-children"><a href="#">Submenu 4</a></li>
    <ul class="sub-menu">
      <li class="menu-item"><a href="#">Sub-sub menu item 3</a></li>
      <li class="menu-item"><a href="#">Sub-sub menu item 4</a></li>
    </ul>
  </ul>
  <li class="menu-item"><a href="#">Menu item 2</a>
  </li>
</ul>

There's as yet no way to define an array of colours in CSS through which we could cycle, because unfortunately while we can select the elements there's no way of iterating through them based on their place within the collection of elements that the selector matches. If they were sibling elements then, in a verbose manner, we could select them:

*,
 ::before,
 ::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
  counter-reset: anchorIndex;
}

a {
  background-color: red;
  color: black;
  padding: 0.5em;
  font-weight: 600;
}

a::before {
  counter-increment: anchorIndex;
  content: "(" counter(anchorIndex, decimal-leading-zero) ") ";
}

a:nth-child(7n + 1) {
  background-color: red;
}

a:nth-child(7n + 2) {
  background-color: orange;
}

a:nth-child(7n+3) {
  background-color: yellow;
}

a:nth-child(7n+4) {
  background-color: green;
}

a:nth-child(7n+5) {
  background-color: blue;
}

a:nth-child(7n+6) {
  background-color: indigo;
}

a:nth-child(7n+7) {
  background-color: violet;
}
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>
<a href="#">link element</a>

Even if the <a> elements themselves weren't siblings, but were all descendents of a group of common sibling elements we could still select the <a> elements, by first selecting the siblings (with similarly verbose syntax to select the siblings, and using a descendent (white-space) combinator still select the <a> elements:

*,
 ::before,
 ::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
  counter-reset: anchorIndex;
  list-style-type: none;
}

a {
  color: black;
  font-weight: 600;
}

a::before {
  counter-increment: anchorIndex;
  content: "(" counter(anchorIndex, decimal-leading-zero) ") ";
}

li:nth-child(7n + 1) a {
  background-color: red;
}

li:nth-child(7n + 2) a {
  background-color: orange;
}

li:nth-child(7n+3) a {
  background-color: yellow;
}

li:nth-child(7n+4) a {
  background-color: green;
}

li:nth-child(7n+5) a {
  background-color: blue;
}

li:nth-child(7n+6) a {
  background-color: indigo;
}

li:nth-child(7n+7) a {
  background-color: violet;
}
<ul>
  <li>
    <div>
      <span>
    <em><a href="#">Link </a></em>
  </span>
    </div>
  </li>
  <li>
    <a href="#">Link</a>
  </li>
  <li>
    <p>
      <a href="#">Link</a>
    </p>
  </li>
  <li>
    <a href="#">Link</a>
  </li>
  <li>
    <div>
      <p><a href="#">Link</a></p>
    </div>
  </li>
  <li>
    <div>
      <span>
    <em><a href="#">Link </a></em>
  </span>
    </div>
  </li>
  <li>
    <a href="#">Link</a>
  </li>
  <li>
    <p>
      <a href="#">Link</a>
    </p>
  </li>
  <li>
    <a href="#">Link</a>
  </li>
  <li>
    <div>
      <p><a href="#">Link</a></p>
    </div>
  </li>
  <li>
    <article><a href="#">Link</a></article>
  </li>
</ul>

So, with that said, the only way I think this is possible is to use JavaScript, or one of its libraries. In native JavaScript:

// initialising the array of colours:
const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

// selecting all the <a> elements on the page:
document.querySelectorAll('a')
  // iterating over the NodeList returned by document.querySelectorAll()
  // with NodeList.prototype.forEach():
  .forEach(
    // using an Arrow function, passing in the arguments of:
    // 'a': a reference to the current Node (the <a>) of the
    // NodeList over which we're iterating,
    // 'i': the index of the current Node in the NodeList:
    (a, i) => {
      // here we retrieve the index of the appropriate colour in the
      // Array of colours, using the remainder operator:
      let index = i % rainbow.length;

      // setting the background-colour of the current <a> element
      // to the appropriate colour:
      a.style.backgroundColor = rainbow[index];
    });
*,
 ::before,
 ::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

ul.main-menu {
  width: 50vw;
  overflow: hidden;
}

ul,
li {
  margin-left: 0.5em;
}

a {
  color: #000;
  font-weight: 700;
  display: block;
}
<ul class="main-menu">
  <li class="menu-item"><a href="#">Menu item 1</a>
  </li>
  <ul class="sub-menu">
    <li class="menu-item">
      <a href="#">Submenu item 1</a>
    </li>
    <li class="menu-item has-children">
      <a href="#">Submenu item 2</a>
    </li>
    <ul class="sub-menu">
      <li class="menu-item">
        <a href="#">Sub-sub menu item 1</a>
      </li>
      <li class="menu-item">
        <a href="#">Sub-sub menu item 2</a>
      </li>
    </ul>
    <li class="menu-item">
      <a href="#">Submenu 3</a>
    </li>
    <li class="menu-item has-children">
      <a href="#">Submenu 4</a>
    </li>
    <ul class="sub-menu">
      <li class="menu-item">
        <a href="#">Sub-sub menu item 3</a>
      </li>
      <li class="menu-item">
        <a href="#">Sub-sub menu item 4</a>
      </li>
    </ul>
  </ul>
  <li class="menu-item">
    <a href="#">Menu item 2</a>
  </li>
</ul>

The same thing is, of course, quite simple using jQuery if you wish to do so:

// initialising the array of colours:
const rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];

// selecting all the <a> elements, and then chaining the css()
// method to set the background-colour of each <a> element:
$('a').css('background-color', function(i) {
  // as the css() method internally iterates over the
  // collection an index - 'i' - is made available to
  // the anonymous function; here we determine the
  // index of the color Array (as in the native JavaScript
  // approach):
  let index = i % rainbow.length;

  // returning the appropriate colour to the css() method:
  return rainbow[index];
});
*,
 ::before,
 ::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

ul.main-menu {
  width: 50vw;
  overflow: hidden;
}

ul,
li {
  margin-left: 0.5em;
}

a {
  color: #000;
  font-weight: 700;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="main-menu">
  <li class="menu-item"><a href="#">Menu item 1</a>
  </li>
  <ul class="sub-menu">
    <li class="menu-item">
      <a href="#">Submenu item 1</a>
    </li>
    <li class="menu-item has-children">
      <a href="#">Submenu item 2</a>
    </li>
    <ul class="sub-menu">
      <li class="menu-item">
        <a href="#">Sub-sub menu item 1</a>
      </li>
      <li class="menu-item">
        <a href="#">Sub-sub menu item 2</a>
      </li>
    </ul>
    <li class="menu-item">
      <a href="#">Submenu 3</a>
    </li>
    <li class="menu-item has-children">
      <a href="#">Submenu 4</a>
    </li>
    <ul class="sub-menu">
      <li class="menu-item">
        <a href="#">Sub-sub menu item 3</a>
      </li>
      <li class="menu-item">
        <a href="#">Sub-sub menu item 4</a>
      </li>
    </ul>
  </ul>
  <li class="menu-item">
    <a href="#">Menu item 2</a>
  </li>
</ul>

References:

Related