53

A few other questions have already addressed how best to apply text-align: justify to get inline-block elements to spread out evenly… for example, How do I *really* justify a horizontal menu in HTML+CSS?

However, the 100% width element that "clears" the line of inline-block elements is given its own line by the browser. I can't figure out how to get rid of that empty vertical space without using line-height: 0; on the parent element.

For an example of the problem, see this fiddle

For my solution that uses line-height: 0;, see this fiddle

The solution I'm using requires that a new line-height be applied to the child elements, but any previously set line-height is lost. Is anyone aware of a better solution? I want to avoid tables so that the elements can wrap when necessary, and also flexbox because the browser support isn't there yet. I also want to avoid floats because the number of elements being spaced out will be arbitrary.

Community
  • 1
  • 1
thirdender
  • 3,891
  • 2
  • 30
  • 33

8 Answers8

90

Updated the "Future" solution info below; still not yet fully supported.

Present Workaround (IE8+, FF, Chrome Tested)

See this fiddle.

Relevant CSS

.prevNext {
    text-align: justify;
}

.prevNext a {
    display: inline-block;
    position: relative;
    top: 1.2em; /* your line-height */
}

.prevNext:before{
    content: '';
    display: block;
    width: 100%;
    margin-bottom: -1.2em; /* your line-height */
}

.prevNext:after {
    content: '';
    display: inline-block;
    width: 100%;
}

Explanation

The display: block on the :before element with the negative bottom margin pulls the lines of text up one line height which eliminates the extra line, but displaces the text. Then with the position: relative on the inline-block elements the displacement is counteracted, but without adding the additional line back.

Though css cannot directly access a line-height "unit" per se, the use of em in the margin-bottom and top settings easily accommodates any line-height given as one of the multiplier values. So 1.2, 120%, or 1.2em are all equal in calculation with respect to line-height, which makes the use of em a good choice here, as even if line-height: 1.2 is set, then 1.2em for margin-bottom and top will match. Good coding to normalize the look of a site means at some point line-height should be defined explicitly, so if any of the multiplier methods are used, then the equivalent em unit will give the same value as the line-height. And if line-height is set to a non-em length, such as px, that instead could be set.

Definitely having a variable or mixin using a css preprocessor such as LESS or SCSS could help keep these values matching the appropriate line-height, or javascript could be used to dynamically read such, but really, the line-height should be known in the context of where this is being used, and the appropriate settings here made.

UPDATE for minified text (no spaces) issue

Kubi's comment noted that a minification of the html that removes the spaces between the <a> elements causes the justification to fail. A pseudo-space within the <a> tag does not help (but that is expected, as the space is happening inside the inline-block element), a <wbr> added between the <a> tags does not help (probably because a break is not necessary to the next line), so if minification is desired, then the solution is a hard coded non-breaking space character &nbsp;--other space characters like thin space and en space did not work (surprisingly).

Nearing a Future Clean Solution

A solution in which webkit was behind the times (as of first writing this) was:

.prevNext {
    text-align: justify;
    -moz-text-align-last: justify;
    -webkit-text-align-last: justify; /* not implemented yet, and will not be */
    text-align-last: justify; /* IE */
}

It works in FF 12.0+ and IE8+ (buggy in IE7).

For Webkit, as of version 39 (at least, might have crept in earlier) it does support it without the -webkit- extension but only if the user has enabled the experimental features (which can be done at chrome://flags/#enable-experimental-web-platform-features). Rumor is that version 41 or 42 should see full support. Since it is not seamlessly supported by webkit yet, it is still only a partial solution. However, I thought I should post it as it can be useful for some.

ScottS
  • 71,703
  • 13
  • 126
  • 146
  • @RoddyoftheFrozenPeas--not according to the first link I posted. I have not officially checked it myself. – ScottS Jul 23 '12 at 22:05
  • I like both solutions, although the lack of Webkit and Opera support is a killer for the second solution. For the first solution, I'm trying to avoid specifically designating the line-height and make it a . If there's a way to do `margin-bottom: (1 unit of inherited line-height);` and `top: (more of the same);` then it might work best… – thirdender Jul 24 '12 at 00:53
  • @thirdender--css cannot access a `line-height` "unit" per se, but I've edited the "Explanation" information in my answer, which may help resolve your worries there about this method. – ScottS Jul 24 '12 at 10:21
  • 6
    It took me an hour to figure out that this method will *only work if you have whitespace* in between your child elements. It did not work with my xsl-generated `formatOutput = true` "minified" HTML. Cf. http://jsfiddle.net/QaS5J/1/ A chain of no-whitespace inline-blocks is treated like a word, I guess, and therefore not justified. – kubi Jan 19 '14 at 13:24
  • @kubi: Excellent observation and comment. I am a bit surprised that the `inline-block` setting itself does not trigger it as a word division, but I guess since it is partially an `inline` element, the browser must figure that if no whitespace exists, the intent is to be wrapping elements of a word itself (characters), and thus does not introduce its own word break. – ScottS Jan 19 '14 at 20:04
  • 2
    @kubi: There is a work around if minification of html is desired. I've updated my answer. Basically, a hard coded non-breaking space in the html is needed (which should not be eliminated in minifying). – ScottS Jan 19 '14 at 20:19
  • 2
    Just a little note on the `nbsp;` between the elements. Adding an `nbsp;` causes a tiny offset in the positioning of the inline-blocks. To prevent that, wrap every `nbsp;` in a`span.inivisible-space` and create a class with `font-size: 0;` – Nikita240 Jan 24 '15 at 08:52
  • 2
    In **IE**, it will only work if the container (in this case **.prevNext**) have a font-size property greater than 0. If it is equals 0, then it won't work – Pablo S G Pacheco Sep 23 '15 at 22:33
  • GREAT catch on the minified HTML problem. Otherwise, the most reliable solution for white-space is simply `font-size: 0` on the parent, and `font-size: 1rem` on the children (the inline-blocks) – lunelson Nov 11 '15 at 10:53
7

Consider the following:

.prevNext {
    display: table;
    width: 100%
}

.prevNext a {
    display: table-cell;
    text-align: center
}

​(Also see the edited fiddle.) Is that what you are looking for? The advantage of this technique is that you can add more items and they will all be centered automatically. Supported by all modern Web browsers.

ACJ
  • 2,499
  • 3
  • 24
  • 27
  • This technique isn't the magic solution it appears to be if you have content of unequal length. I modified the fiddle to show this. You can clearly see that the spacing around the short links is much less than around the long links. http://jsfiddle.net/arkid77/5vcgpkeL/2/ – AdamJones Apr 14 '15 at 12:19
  • Not exactly 'Justified'. – dewwwald Sep 30 '17 at 22:28
4

First off, I like the approach of the pseudo-element in order to keep the markup semantic. I think you should stick with the overall approach. It's far better than resorting to tables, unnecessary markup, or over the top scripts to grab the positioning data.

For everyone stressed about text-align being hacky - c'mon! It's better that the html be semantic at the expense of the CSS than vice versa.

So, from my understanding, you're trying to achieve this justified inline-block effect without having to worry about resetting the line-height every time right? I contend that you simply add

.prevNext *{
    line-height: 1.2;  /* or normal */
}

Then you can go about coding as though nothing happened. Here's Paul Irish's quote about the * selector if you're worried about performance:

"...you are not allowed to care about the performance of * unless you concatenate all your javascript, have it at the bottom, minify your css and js, gzip all your assets, and losslessly compress all your images. If you aren't getting 90+ Page Speed scores, it's way too early to be thinking about selector optimization."

Hope this helps!

-J Cole Morrison

J Cole Morrison
  • 808
  • 8
  • 13
  • I am really liking the `line-height: normal;` :-p It dovetails nicely with what I was trying to do. It doesn't get rid of the `line-height: 0;`, but resets the line-height to a normalized value. If someone else wants to override the line-height they can… Still trying to grok @MoinZaman's Sitepoint link, and hoping there might be some other answer that doesn't require the `line-height: 0;`, but this feels close. – thirdender Jul 24 '12 at 05:43
  • 2
    Hey Thirdender! Yeah, I looked through some of the different things, but often I've found that, especially for CSS, the simplest approach works best. It's nice that changing around the `display: table` and `display: table-cell` works, but those lead to an even wider array of potential descendant css conflicts. Go for maintainable and understandable in my opinion (and pray for `flexbox`)! – J Cole Morrison Jul 24 '12 at 18:11
  • It _might_ help performance some to use: `.prevNext > *` so it is only looking at the direct children rather than _all_ descendents of `.prevNext`. Of course, this is still a `line-height` reset, which in the question the OP said he was trying to avoid. – ScottS Jul 24 '12 at 22:55
  • Agreed on the performance thing! With respect to the reset issue: The OP asks IF there's a better solution and seeks to avoid tables, flexbox, and floats - not specifically a line-height reset. – J Cole Morrison Jul 24 '12 at 23:13
  • Yes… I was trying to avoid a line-height reset (because it should inherit from the parent elements), but even more importantly I was trying to avoid specifying a fixed line-height. `line-height: 1.2em` or `line-height: 1.2` is _close_ to `line-height: normal`, but each font defines it's own 'normal' line-height, so 1.2 is only an approximation. I didn't realize `line-height: normal` was available as an option (I tried 'default' and 'auto', but missed 'normal' in my Google searches). I'd still like a better solution, but I think this works in 9/10 use cases (which works for my SCSS mixin). – thirdender Jul 25 '12 at 16:05
  • Gotcha, my bad. For this new solution are you looking for something more "semantically" correct or something more "simple" out of curiosity? – J Cole Morrison Jul 25 '12 at 22:13
  • @thirdender--I don't believe fonts explicitly define 'normal'. The [spec states](http://www.w3.org/TR/CSS2/visudet.html#line-height) that `normal`: "Tells user agents to set the used value to a 'reasonable' value based on the font of the element," and further recommended to be "between 1.0 to 1.2." So the _browser_ is setting the `normal` value based off a backend calculation that involves the font. This is why it is good to explicitly set `line-height`, as it helps standardize the cross browser experience and give ["vertical rhythm"](http://www.alistapart.com/articles/howtosizetextincss/). – ScottS Jul 26 '12 at 01:35
  • "For obscure reasons rooted in the history of the TrueType and OpenType font formats, every webfont carries three sets of linespacing values." [source](http://webfonts.info/webfonts-know-how/part-2-problem-line-height) That value is taken into consideration when calculating the `line-height: normal;`, but that is also the direct cause of the "vertical rhythm" issues. I'm assuming in most cases when line-height is crucial to layout, it will (or can) be specified inside whatever area this CSS is applied to. – thirdender Jul 26 '12 at 20:46
  • @thirdender--good source reference. I think we are on the same page, that the font defines something in _itself_ which the browser through some "backend calculation" (to quote myself) uses to actually define `normal`. You were emphasizing the font's role, I was emphasizing the browser's role, but I think we end up at the same place: `normal` is not explicit based off font or browser independently, but only in combination. – ScottS Jul 28 '12 at 12:32
  • Yeah, the important thing is that not all fonts have the same line-height. I think that was intentional to deal with different font rendering systems (look at the history of Windows vs Mac font rendering…). So if you're attempting a layout with "vertical rhythm", your line-height should be set as close to the content as possible. Since the `text-align: justify` part is used for layout, I can see `line-height` being set explicitly after this in the CSS. I'd still like to get rid of the `line-height: 0`, but without needing to pass in an explicit line-height from the parent elements. – thirdender Jul 28 '12 at 17:55
  • For sure! As diverse web typography becomes more and more center-stage getting away from odd uses of `text-align` and friends will be harder to `justify` (haha!). I wish they'd call `text-align` `inline-align` or something. `getComputedStyle` is a fun API to hook into if you need to read up on the current state of an element if the typography is changing. Just remember the shortest distance between two points is a straight-line - don't get too caught up in everything being syntactically/semantically perfect. Utopian coding in a dystopian environment is ... ? – J Cole Morrison Jul 30 '12 at 03:55
  • I'll probably use `line-height: normal` in my SCSS mixin, but I'm really hoping to use `text-align-last: justify` as mentioned in the accepted answer soon. Some years from now, it'd be great for all browsers to implement the new CSS3 flexbox standard, but in the meantime… It looks like `text-align-last: justify` will work in all browsers as far back as IE6. – thirdender Jul 30 '12 at 08:37
1

Attempting to text-align for this problem is pretty hackish. The text-align property is meant to align inline content of a block (specifically text) -- it is not meant to align html elements.

I understand that you are trying to avoid floats, but in my opinion floats are the best way to accomplish what you are trying to do.

Michael Frederick
  • 16,664
  • 3
  • 43
  • 58
  • I was thinking the same thing; it's misusing one property for the sake of not using another property that is actually made for this very purpose. – Vin Burgh Jul 23 '12 at 21:51
  • 2
    @VinnyBurgh--I would tend to disagree. Floats are not designed to "justify" elements on a line. Floats can be forced to pseudo-justify elements, but only if fixed widths are involved. His use case demands allowing for more than just two elements, one right, one left, based off his comment to Ali Gajani's answer. It also appears to not involve fixed width elements. – ScottS Jul 23 '12 at 22:10
  • I agree that floats aren't designed to justify elements, but text-align:justify isn't designed for that purpose either. Both properties were introduced in CSS1 and I'm sure this sort of use-case wasn't appropriate nor necessary at the time of their writing. I am enjoying the OP's question, regardless. – Vin Burgh Jul 23 '12 at 22:21
  • 2
    @VinnyBurgh, the new [CSS3 flexbox standard] (http://www.w3.org/TR/css3-flexbox/) fills the bill perfectly, but the last flexbox implementation fell short and never had IE support (aside from a JS shim which gave me some issues)… – thirdender Jul 24 '12 at 00:16
0

In your example you have line-height:1.2, without a unit. This may cause issues. If you're not using borders you could give the parent and the children a line-height of 0.

The other options I can think of are:

  1. Use display:table on the parent and display:table-cell on the children to simulate table like behaviour. And you align the first item left, and the last one right. See this fiddle.
  2. Use javascript to do a count of the nav children and then give them a equally distributed width. eg. 4 children, 25% width each. And align the first and last items left and right respectively.
  3. There is a way to evenly distribute the items but is a convoluted method that requires some non breaking spaces to be carefully placed in the html along with a negative margin and text-align:justify. You could try and adapt it the the nav element. See example here.
Moin Zaman
  • 25,281
  • 6
  • 70
  • 74
  • The borders are just there to display the bounding box of the parent container. Also, giving all elements `line-height: 0` is a bad idea for layout (it can cause the text to render over the previous and next siblings, see [this fiddle](http://jsfiddle.net/hyudd/)). For #1, as mentioned in the question, I'm trying to avoid tables so the elements can wrap if necessary. For #2, really trying to avoid Javascript and make this a pure CSS solution. I want something I can roll out, set and forget, and not worry about if/when Javascript is available. – thirdender Jul 23 '12 at 22:47
  • @thirdender--found a workaround solution that should meet your needs and updated my answer to show it. – ScottS Jul 23 '12 at 23:01
  • @MoinZaman, neither of your links work… They both point to http://www.jsbin.com/edison/1/edit – thirdender Jul 23 '12 at 23:09
0

Your fiddle is awfully specific. It seems to me for your case this CSS would work well:

.prevNext {
    border: 1px solid #ccc;
    position: relative;
    height: 1.5em;
}

.prevNext a {
    display: block;
    position: absolute;
    top: 0;
}

.prevNext a:first-child {
    left: 0;
    text-align: left;
}
.prevNext a:last-child {
    right: 0;
    text-align: right;
}
​
artlung
  • 33,305
  • 16
  • 69
  • 121
0

As stated by @Scotts, the following has been implemented inside Chrome, without the -webkit part , which I really loved btw, specially since we need to get rid of the -browser-specific-shǐt real soon.

.prevNext {
    text-align: justify;
    -moz-text-align-last: justify;
    -webkit-text-align-last: justify; /* not implemented yet, and will not be */
    text-align-last: justify; /* IE + Chrome */
}

Note: Though still the Safari and Opera don't support it yet (08-SEPT-16).

Mohd Abdul Mujib
  • 13,071
  • 8
  • 64
  • 88
-2

I think the best way would be to create the clickable element with a specific class/id and then assign float:left or float:right accordingly. Hope that helps.

Igor Ivancha
  • 3,413
  • 4
  • 30
  • 39
Ali Gajani
  • 14,762
  • 12
  • 59
  • 100
  • I'm trying to avoid floats… For the fiddle I linked floats would work because there are only two elements ("previous", "next"). I created a [new fiddle](http://jsfiddle.net/89bnF/7/) showing 4 elements. The goal is to work with any number of elements. – thirdender Jul 21 '12 at 22:13