2

I'm trying to make ul menu with :before pseudoelement on li tags. Text inside :before's content is a different text for different rows. I want make this text right-aligned for each li, like this:

  pseudo_x li
 pseudo_xx li
pseudo_xxx li

li:nth-child(n):before {
  content: "===";
  border: 1px dotted red;
}

li:nth-child(2n):before {
  content: "-----------";
  border: 1px dotted red;
}
<ul>
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
</ul>

*** Update

Thanks for answers. But I can't use list-style-type because I will format the :before's content. Also I can't use one width for all contents. Because the idea I writing all this, is to use content for display count of rows inside li tags, like this:

var rows = document.getElementsByTagName('li');
for(var i=0;i<rows.length;i++)
  rows[i].setAttribute("c", "rows: "+"1".repeat(Math.random()*10|0))
li{
    list-style-type: none;
}
li:before{
    color: white;
    content: attr(c);
}
.red:before{background:red}
.orange:before{background:orange}
.blue:before{background:blue}
.green:before{background:green}
<ul>
  <li class="red">First</li>
  <li class="orange">Second</li>
  <li class="blue">Third</li>
  <li class="green">Fourth</li>
</ul>
maloun
  • 39
  • 4
  • Other than making the ::before elements inline-block, and then specifying a large enough width, I don't see any way of doing this. – CBroe Nov 09 '22 at 08:07
  • 1
    Instead of writing your content into css you should use sth like a nav-tag and inside a grid layout with your content. – LelilolZH Nov 09 '22 at 08:32
  • @LelilolZH can you show an example of making content by grid layout inside of li? – maloun Nov 09 '22 at 09:41
  • This should be a double colon, not a single one. https://developer.mozilla.org/en-US/docs/Web/CSS/::before – Rob Nov 09 '22 at 13:03

3 Answers3

2

One way is to make use of the CSS @counter-style at-rule:

/* custom properties for styling the gutter,
   this allows ancestor elements access to
   the sizing of their descendant (since
   the properties are available outside of
   those descendants): */
:root {
  /* the spacing between the generated content
     counter and the <li> itself: */
  --counterGutter: 0.5em;
  /* the width/inline-size of the counter
     pseudo-element: */
  --counterWidth: 4em;
}

/* defining the counter to use, and assigning a
   name to that counter (here: 'lines'): */
@counter-style lines {
  /* causes the counter to cycle through the
     provided symbols: */
  system: cyclic;
  /* white-space separated list of symbols that
     the list counter should cycle through: */
  symbols: "===" "-----";
}

ul {
  /* resetting the counter for the <li>: */
  counter-reset: liCounter;
  /* removing the default counter: */
  list-style-type: none;
}

li {
  /* using logical properties (see the references)
     to set a margin of 0.5em before and after the
     <li> elements on the block axis: */
  margin-block: 0.5em;
  /* setting a margin on the inline axis equal to
     the inline-size of the counter: */
  margin-inline-start: var(--counterWidth);
  /* to allow the pseudo-element child to be
     positioned relative to this ancestor: */
  position: relative; 
}

li::before {
  border: 1px dotted #f00;
  /* using the counter() function to define the
     counter, and counter-style, to use: */
  content: counter(liCounter, lines);
  /* incrementing the relevant counter: */
  counter-increment: liCounter;
  /* setting the inline-size (logical property,
     see references) to the defined custom-
     property: */
  inline-size: var(--counterWidth);
  position: absolute;
  /* aligning the text using - again - logical
     properties, this is equal to right-aligned
     in the English (and Latin) language(s): */
  text-align: end;
  /* moving the element by the size of the defined
     --counterGutter and its own width (100%), using
     -1 to move the element before the content of
     its ancestor on the inline axis: */
  translate: calc(-1 * (var(--counterGutter) + 100%));
}
<ul>
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
</ul>

JS Fiddle demo.

Following the comment (below) from OP:

…do you know how to save the original width of [::before] content?

There are a number of options to do this, which allows the inherent width of the counter glyphs to show, all of which have problems that may make them untenable in practice.

The first is my preference – but requires the use of subgrid, which is currently supported only in Safari and Firefox:

@counter-style lines {
  system: cyclic;
  symbols: "===" "-----";
  suffix: " ";
}

ul {
  counter-reset: liCounter;
  /* setting a display grid on the <ul> */
  display: grid;
  /* setting 0.5em gap between adjacent cells, on
     both row and column axes: */
  gap: 0.5em;
  /* defining two columns, the first of which is as
     small as possible while fitting the content and
     the second of which takes one fraction of the
     remaining space available: */
  grid-template-columns: min-content 1fr;
  list-style-type: none;
}

li {
  /* using display: grid on the child <li> elements
     as well: */
  display: grid;
  /* placing the <li> element by having it start in
     the first column and to span two columns of the
     parent element: */
  grid-column: 1 / span 2;
  /* setting the columns of the parent <ul> as the
     columns of the <li> grid: */
  grid-template-columns: subgrid;
}

li::before {
  border: 1px dotted #f00;
  content: counter(liCounter, lines);
  counter-increment: liCounter;
  /* justifying the content to the end
     of the inline axis, so the border
     wraps only the content, not the
     whole cell: */
  justify-self: end;
}
<ul>
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
</ul>

JS Fiddle demo.

Another option is to simply omit the sizing properties of the pseudo-element as used in the first code; unfortunately there's no obvious way to have access to the inherent sizes of the pseudo-elements. Without access to the size, the layout is a little fragile and may break at certain points:

:root {
  --counterGutter: 0.5em;
}

@counter-style lines {
  system: cyclic;
  symbols: "===" "-----";
  suffix: " ";
}

ul {
  counter-reset: liCounter;
  list-style-type: none;
}

li {
  margin-block: 0.5em;
  /* a dimension that's probably big
     enough to fit the pseudo-element
     inside, without being clipped
     by overflow rules or being placed
     over the <li> content: */
  margin-inline-start: 5em;
  position: relative;
}

li::before {
  border: 1px dotted #f00;
  content: counter(liCounter, lines);
  counter-increment: liCounter;
  position: absolute;
  text-align: end;
  translate: calc(-1 * (var(--counterGutter) + 100%));
}
<ul>
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
  <li>Fourth</li>
</ul>

JS Fiddle demo.

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • thanks for answer, but do you know how to save the original width of :before content? – maloun Nov 09 '22 at 09:40
  • 1
    You want this: https://jsfiddle.net/davidThomas/8db4ck9j/1/? It's easy enough to do, but you have to remember there's no way (that I know of) to know the inherent width of the pseudo-element, so the `margin-inline-start` on the `
  • ` would have to be an arbitrary magic number. If you're okay with that, then this version should be okay.
  • – David Thomas Nov 09 '22 at 09:45
  • Oh god, seems this is exactly what I need! Hope I can use it with js like in first post snippet. Your css is extremely new for me. Thank you! – maloun Nov 09 '22 at 09:52
  • 1
    You're welcome, I've added another couple of demos to the answer; subgrid seems to do what you need but it's only in Safari and Firefox (so far, dammit Google...). – David Thomas Nov 09 '22 at 10:12