Aligning decimal points in HTML

HtmlCss

Html Problem Overview


I have a table containing decimal numbers in one column. I'm looking to align them in a manner similar to a word processor's "decimal tab" feature, so that all the points sit on a vertical line.

I have two possible solutions at the moment but I'm hoping for something better...

Solution 1: Split the numbers within the HTML, e.g.

<td><div>1234</div><div class='dp'>.5</div></td>

with

.dp { width: 3em; }

(Yes, this solution doesn't quite work as-is. The concept is, however, valid.)

Solution 2: I found mention of

<col align="char" char=".">

This is part of HTML4 according to the reference page, but it doesn't work in FF3.5, Safari 4 or IE7, which are the browsers I have to hand. It also has the problem that you can't pull out the numeric formatting to CSS (although, since it's affecting a whole column, I suppose that's not too surprising).

Thus, anyone have a better idea?

Html Solutions


Solution 1 - Html

See this article by Krijn Hoetmer for your options and how to achieve this. The essence of this solution is to use CSS and JS to achieve this:

(function() {
  var currencies = /(\$|€|&euro;)/;
  var leftWidth = 0, rightWidth = 0;
  for(var tableCounter = 0, tables = document.getElementsByTagName("table");
      tableCounter < tables.length; tableCounter++) {
    if(tables[tableCounter].className.indexOf("fix-align-char") != -1) {
      var fCols = [], leftPart, rightPart, parts;
      for(var i = 0, cols = tables[tableCounter].getElementsByTagName("col"); i < cols.length; i++) {
        if(cols[i].getAttribute("char")) {
          fCols[i] = cols[i].getAttribute("char");
        }
      }
      for(var i = 0, trs = tables[tableCounter].rows; i < trs.length; i++) {
        for(var j = 0, tds = trs[i].getElementsByTagName("td"); j < tds.length; j++) {
          if(fCols[j]) {
            if(tds[j].innerHTML.indexOf(fCols[j]) != -1) {
              parts = tds[j].innerHTML.split(fCols[j]);
              leftPart = parts.slice(0, parts.length -1).join(fCols[j]);
              leftPart = leftPart.replace(currencies, "<span class='currency'>$1</span>");
              rightPart = fCols[j] + parts.pop();
              tds[j].innerHTML = "<span class='left'>" + leftPart + "</span><span class='right'>" + rightPart + "</span>";
            } else {
              tds[j].innerHTML = tds[j].innerHTML.replace(currencies, "<span class='currency'>$1</span>");
              tds[j].innerHTML = "<span class='left'>" + tds[j].innerHTML + "</span>";
            }
            tds[j].className = "char-align";
            var txt = document.createTextNode(tds[j].firstChild.offsetWidth);
            if(leftWidth < tds[j].firstChild.offsetWidth) {
              leftWidth = tds[j].firstChild.offsetWidth;
            }
            if(tds[j].childNodes[1]) {
              txt = document.createTextNode(tds[j].childNodes[1].offsetWidth);
              if(rightWidth < tds[j].childNodes[1].offsetWidth) {
                rightWidth = tds[j].childNodes[1].offsetWidth;
              }
            }
          }
        }
      }
    }
  }
  // This is ugly and should be improved (amongst other parts of the code ;)
  var styleText = "\n" +
      "<style type='text/css'>\n" +
      "  .fix-align-char td.char-align { width: " + (leftWidth + rightWidth) + "px; }\n" +
      "  .fix-align-char span.left { float: left; text-align: right; width: " + leftWidth + "px; }\n" +
      "  .fix-align-char span.currency { text-align: left; float: left; }\n" +
      "  .fix-align-char span.right { float: right; text-align: left; width: " + rightWidth + "px; }\n" +
      "</style>\n";
  document.body.innerHTML += styleText;
})();

table {
  border-collapse: collapse;
  width: 600px;
}
th {
  padding: .5em;
  background: #eee;
  text-align: left;
}
td {
  padding: .5em;
}
#only-css td.char-align {
  width: 7em;
}
#only-css span.left {
  float: left;
  width: 4em;
  text-align: right;
}
#only-css span.currency {
  float: left;
  width: 2em;
  text-align: left;
}
#only-css span.right {
  float: right;
  width: 3em;
  text-align: left;
}

<table id="only-css">
  <thead>
    <tr>
      <th>Number</th>
      <th>Description</th>
      <th>Costs</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>Lorem ipsum dolor sit amet</td>
      <td class="char-align">
        <span class="left">
          <span class="currency">$</span>3
        </span>
        <span class="right">,99</span>
      </td>
    </tr>
    <tr>
      <td>2</td>
      <td>Consectetuer adipiscing elit</td>
      <td class="char-align">
        <span class="left">
          <span class="currency">$</span>13
        </span>
        <span class="right">,95</span>
      </td>
    </tr>
    <tr>
      <td>3</td>
      <td>Pellentesque fringilla nisl ac mi</td>
      <td class="char-align">
        <span class="left">
          <span class="currency">$</span>4
        </span>
        <span class="right"></span>
      </td>
    </tr>
    <tr>
      <td>4</td>
      <td>Aenean egestas gravida magna</td>
      <td class="char-align">
        <span class="left">
          <span class="currency">$</span>123
        </span>
        <span class="right">,999</span>
      </td>
    </tr>
  </tbody>
</table>

Solution 2 - Html

Another way to format a number would be like this: 35<span style="visibility: hidden">.000</span>. That is, write it out with the full decimal expansion, but write the trailing decimals in invisible ink. That way you don't have to worry about the width of the decimal point.

Solution 3 - Html

I'm surprised that in 10 years of answers to this question, nobody ever mentioned the Unicode character 'FIGURE SPACE' (U+2007, &#8199;)

It's a whitespace character that is designed (by font authors, if they follow the standard) to be the same width as digits and to keep its spacing, like its more famous cousin the No-Break Space. You can use it to pad numbers to a certain string size, either on the left or on the right hand side, taking care of aligning the column or div on the same side.

Examples, both left-aligned and left-padded with figure spaces:

<p style="font-family: sans-serif">
  10000 <br>
  &#8199;&#8199;123.4 <br>
  &#8199;&#8199;&#8199;&#8199;3.141592
</p>

<p style="font-family: serif">
  10000 <br>
  &#8199;&#8199;123.4 <br>
  &#8199;&#8199;&#8199;&#8199;3.141592
</p>

Solution 4 - Html

Cheat; benefit of this solution: also works for proportional fonts. Have one extra column and split the integer part from the decimal separator and the decimals. Then use this css and combine two columns in the header row:

table {border-collapse:collapse;}
td {padding:0px;margin:0px;border:0px;}
td+td {text-align:right;}
td, td+td+td {text-align:left;}

<table>
    <tr><th>Name</th><th colspan=2>Height</th></tr>
    <tr><td>eiffeltower</td> <td>324</td> <td></td></tr>
    <tr><td>giraffe</td> <td>5</td> <td>,30</td></tr>
    <tr><td>deer</td> <td>1</td> <td></td></tr>
    <tr><td>mouse</td> <td>0</td> <td>,03</td></tr>
</table>

Caveat: It isn't guaranteed to work. For example, on Safari 14 in 2021:

https://i.stack.imgur.com/jxZJ0.png" width="300" title="screenshot of result on Safari 14" />

Solution 5 - Html

I played around with jQuery & came up with this...

.right {
    text-align: left;
}
.left {
    float:left;
    text-align: right;
    width:10em;
}

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>

<table width="600" border="1">  
  <tr><th></th><th>Aligned Column</th></tr>
  <tr><th>1st Row</th><td class='aBDP'>1.1</td></tr>
  <tr><th>2nd Row</th><td class='aBDP'>10.01</td></tr>  
  <tr><th>3rd Row</th><td class='aBDP'>100.001</td></tr>  
  <tr><th>4th Row</th><td class='aBDP'>1000.0001</td></tr>
</table>

$(document).ready(function() {
  $('.aBDP').each(function() {
    var wholePart, fractionPart;
    wholePart = Math.floor($(this).text()-0);
    fractionPart = Math.floor(($(this).text() % 1)*10000 + 0.5) / 10000 + "";
    html  = '<span class="left">' + wholePart + '.' + '</span>';
    html += '<span class="right">' + fractionPart.substring(2) + '</span>';
    $(this).html(html); 
  })
})

It seemed to work.

Solution 6 - Html

can you just print the numbers so that they always have the same number of decimal places, and right align them?

Solution 7 - Html

Thousands of years ago (or 2-3) I wrote a jQuery shim that emulates align="char" which still seems to work. It uses CSS padding and accounts for colspans, so it's moderately clever, but it's really not very pretty code (I was just starting out in javascript back then). I'd love for someone to rewrite it (and take all the credit).

In the mean time, see if this helps you: https://gist.github.com/mattattui/f27ffd25c174e9d8a0907455395d147d

Trivia: The reason that browsers don't properly support column styles is that tables are 2D data structures and the DOM (which is what Javascript and CSS operate on, and how HTML5 is defined) is purely hierarchical and therefore can't represent both columns and rows. Instead it simply defines rows and cells, and doesn't represent columns at all.

Solution 8 - Html

I love short answers, even though the long ones are important too, so I liked;

35<span style="color:transparent">.000</span>

and would just like to add;

<TD><div style='float:right;'><?php echo number_format($totalAmount,2); ?></div></TD>

just to throw php into the mix. Much depends on fixed width fonts, still, but the latter works for me. Since data oft is already tabular, adding another table within a cell is just too much typing and hard to maintain.

Solution 9 - Html

If the numbers are monospaced, javascript could be used to adjust the padding on the cell (in ems), depending on the number of digits before the decimal point. Otherwise, it could be tricky.

Solution 10 - Html

The function made by Krijn Hoetmer interferes with prettyPhoto ( http://www.no-margin-for-errors.com/projects/prettyphoto-jquery-lightbox-clone/ ) so I made a jQuery version. The currency part is removed as it should be made dynamic instead of replacing strings based on predefined currencies.

Needed is the empty function from phpjs: http://phpjs.org/functions/empty:392 .

The jQuery used, is version 1.6.

/* This function will align table columns on the char if in the col from the 
 * colgroup has the property 'align="char"' and a attribute 'char'. The alignment
 * is done on the first occurence of the specified char.
 * 
 * The function is inspired from:
 * 
 * http://krijnhoetmer.nl/stuff/javascript/table-align-char/
 * http://stackoverflow.com/questions/1363239/aligning-decimal-points-in-html
 */
function alignNumbers()
{
  var table; /* This will store the table currently working on . */
  var i = 0; /* Every column can have it's own width, the counter makes the class name unique. */

  /* Get all tables for which the alignment fix must be done. 
   *
   * Note: this could even be further optimized by just looking for tables where
   * there is a a col with 'align="char"'. 
   */
  $('table.fix-align-char').each(function(index)
  {
    table = $(this);

    /* All table columns are fetched to have a correct index, without it it's
     * hard to get the correct table cells.
     */
    $(this).find('col').each(function(index)
    {
      /* Only those table cells are changed for which the alignment is set to
       * char and a char is given.
       */
      if ($(this).prop('align') == 'char' && !empty($(this).attr('char')))
      {
        /* Variables for storing the width for the left and right part (in pixels). */
        var left_width = 0, right_width = 0;
        var col, left_part, right_part, parts, new_html;
        i++; /* Increase the counter since we are working on a new column. */
        col = $(this);

        /* For the col index + 1 (nth-child starts counting at 1), find the table
         * cells in the current table.
         */
        table.find('> tbody > tr > td:nth-child('+ (index + 1) +')').each(function(index)
        {
          /* Split the html on the specified char. */
          parts = $(this).html().split(col.attr('char'));
          new_html = '';


          /* The first element is always the left part. The remaining part(s) are
           * the right part. Should there be more chars in the string, the right
           * parts are rejoined again with the specified char. 
           */
          left_part = parts.shift();
          right_part = parts.join(',');

          /* Add a left part to the new html if the left part isn't empty*/
          if (!empty(left_part))
          {
            new_html = new_html + '<span class="left">' + left_part + '</span>';
          }

          /* Add the specified char and the right part to the new html if 
           * the right part isn't empty*/
          if (!empty(right_part))
          {
            new_html = new_html + col.attr('char') + '<span class="right">' + right_part + '</span>';
          }

          /* If there is a new html, the width must be determined and a class is
           * added.
           * 
           * Note: outerWidth is used instead of width so padding, margin and
           * borders are taken into account.
           */
          if (!empty(new_html))
          {
            $(this).html(new_html); /* Set the new html. */
            $(this).addClass('char-align-' + i); /* Add a class to the table cell. */

            /* Get the left span to determine its outer width. */
            leftSpan = $(this).children('.left');

            if (!empty(leftSpan) && left_width < leftSpan.outerWidth())
            {
              left_width = leftSpan.outerWidth();
            }

            /* Get the right span to determine its outer width. */
            rightSpan = $(this).children('.right');

            if (!empty(rightSpan) && right_width < rightSpan.outerWidth())
            {
              right_width = rightSpan.outerWidth();
            }

          }

        });

        /* Only if any width is larger then 0, add a style. */
        if (left_width > 0 || right_width > 0)
        {
          style_text = '<style type="text/css">.fix-align-char td.char-align-' + (i) + ' span.left { float: left; text-align: right; width: ' + (left_width) + 'px; }\n.fix-align-char td.char-align-' + (i) + ' span.right { float: right; text-align: left; width: ' + right_width + 'px; }</style>';
          $('head').append(style_text);
        }

      }
    });
  });

}

$(document).ready(function(){
  alignNumbers();
});

Solution 11 - Html

I have used JavaScript to fix this issue... This is my HTML.

<body>
<table id="nadis">
 
</tr>
</table>

</body>

This is my JavaScript.

var numarray = ["1.1", "12.20", "151.12", 1000.23,12451];
var highetlen = 0;
for(var i=0; i<numarray.length; i++){
	var n = numarray[i].toString();
  var res= n.split(".");
  n = res[0];

  if(highetlen < n.length){
  	highetlen = n.length;
  }
  
}

for(var j=0; j<numarray.length; j++){
	var s = numarray[j].toString();
  var res= s.split(".");
  s = res[0];

	if(highetlen > s.length){
  var finallevel = highetlen - s.length;

  var finalhigh = "";
  for(k=0;k<finallevel;k++){
  finalhigh = finalhigh+ '&#160; ';
  }
  	numarray[j] = finalhigh + numarray[j];
  }
  var nadiss = document.getElementById("nadis");
  nadiss.innerHTML += "<tr><td>" + numarray[j] + "</td></tr>";
}

Solution 12 - Html

A serious trouble in the previous approaches, is that only think in visual, but do not in other needs or uses of tables as sorting or filtering, where pure data is important.

Unfortunately CSS4 are not available yet. Then a valid solution could be pass the value and units or type unit in data attributes on td cell.

<!-- HTML-->
<table>
  <tbody>
    <tr>
      <td data-value="1876.67542" data-unit="USD"></td>
    </tr>
  </tbody>
</table>

If a cell have a data value, it must read with javascript and updated to the decimal numbers that we requires.

// Javascript
let $td_value = document.querySelectorAll( 'td[data-item]' );
Array.from( $td_value ).forEach( $r => {
  $r.textContent = parseFloat( $r.getAttribute('data-value') ).toFixed(2);
});

At the end, when we have normalized data, they will looks great with mono fonts and with their units placed using css selectors as before or after.

/* CSS */
td[data-value]{
  font-family: monospace;
  text-align: right;
}
td[data-unit]::after{
  content: attr(data-unit]);
  font-size: 85%;
  padding-left: .2em;
  opacity: .6;
}

I put an extended example in: https://jsfiddle.net/jam65st/wbo63xpu/12/

Solution 13 - Html

Ugly workaround but will save you from writing a lot of code: You can find the max number in the array (list) of prices, then you can take the number of its digits and set inline style "width": (maxNumberDigits * 10)px - this is the ugly part! And the container of this data (cell if its table) should have additionally

display:flex;
justify-content: flex-end;

Result:

Result

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionijwView Question on Stackoverflow
Solution 1 - HtmlDan DiploView Answer on Stackoverflow
Solution 2 - HtmlDavid LeppikView Answer on Stackoverflow
Solution 3 - HtmlTobiaView Answer on Stackoverflow
Solution 4 - Htmlard jonkerView Answer on Stackoverflow
Solution 5 - HtmlmilarView Answer on Stackoverflow
Solution 6 - HtmlRob Fonseca-EnsorView Answer on Stackoverflow
Solution 7 - HtmlinanimattView Answer on Stackoverflow
Solution 8 - HtmlSoup CupView Answer on Stackoverflow
Solution 9 - HtmlEricView Answer on Stackoverflow
Solution 10 - HtmlMondaneView Answer on Stackoverflow
Solution 11 - HtmlNarendra SagadevanView Answer on Stackoverflow
Solution 12 - HtmlJ. A. MendezView Answer on Stackoverflow
Solution 13 - HtmlGoce KabovView Answer on Stackoverflow