Flexible multiline definition lists with 2 lines of CSS 2.1

If you’ve used definition lists (<dl>) you’re aware of the problem. By default, <dt>s and <dd>s have display:block. In order to turn them into what we want in most cases (each pair of term and definition on one line) we usually employ a number of different techniques:

  • Using a different <dl> for each pair: Style dictating markup, which is bad
  • Floats: Not flexible
  • display: run-in; on the <dt>: Browser support is bad (No Firefox support)
  • Adding a <br> after each <dd> and setting both term and definition as display:inline: Invalid markup. Need I say more?

If only adding <br>s was valid… Or, even better, what if we could insert <br>s from CSS? Actually, we can!

As you might be aware, the CR and LF characters that comprise a line break are regular unicode characters that can be inserted anywhere just like every unicode character. They have the unicode codes 000D and 000A respectively. This means they can also be inserted as generated content, if escaped properly. Then we can use an appropriate white-space value to make the browser respect line breaks only in that part (the inserted line break). It looks like this:

dd:after {
	content: '\A';
	white-space: pre;

Note that nothing above is CSS3. It’s all good ol’ CSS 2.1.

Of course, if you have multiple <dd>s for every <dt>, you will need to alter the code a bit. But in that case, this formatting probably won’t be what you want anyway.

Edit: As Christian Heilmann pointed out, HTML3 (!) used to have a compact attribute on <dl> elements, which basically did this. It is now obsolete in HTML5, like every other presentational HTML feature.

You can see a live result here:

Tested to work in IE8+, Chrome, Firefox 3+, Opera 10+, Safari 4+.

  • rkrupinski

    Or we can just move s to s


    Valid markup + 1 line of css + working everywhere….

    Ok, forget about it, just kidding :P

    • http://leaverou.me Lea Verou

      This basically does exactly the same, with (arguably) presentational markup.

  • Andy

    How about this?

    dt {float: left; clear: left}

    Might be missing the point though since i cant see your example on my android. Not quite sure of what you mean with floats not being flexible. Could you elaborate?

    • http://leaverou.me Lea Verou

      I generally try to avoid floats when I can, as (in my experience, YMMV) there will always be some weird combination of inside/surrounding content that will break them no matter how extensively you test them. 

    • http://twitter.com/lowbatteries Steven Simmons

      I’ve always used the float method for this sort of thing, but it’s nice to have options.

    • Gunnar Bittersmann

       Floating fails when the dt content does not fit into a single line.

  • http://www.facebook.com/david.arceneaux David Arceneaux

    Aren’t you trying to do here what “display:run-in” does?

  • SelenIT

     Lea, but wasn’t display:run-in (http://www.w3.org/TR/css3-box/#run-in-boxes) invented exactly for that sort of tasks (see the 2nd example from http://wheresrhys.co.uk/2010/02/use-for-display-runin/ or http://jsfiddle.net/TtKPd/1/, for instance)? May be, looking to the future, we’d better try to find a way to make it work in Gecko?

    • http://leaverou.me Lea Verou

      Yup, display: run-in would be perfect for this. It didn’t cross my mind originally as I had ruled it out due to browser support. Will add to the list, thanks!

  • Robson Sobral

    Hi, Lea. I already had this issue too and fixed using almost the same code:

    dt:before {content: ”; display: block;}

    But your approach is better, since isn’t a good idea to have inline and block siblings.

    Since one DT can be followed by several DD, I suggest you to use:

    dt:before {content: ‘DA’;
    white-space: pre;}dt:first-child:before {content: normal;}

    • http://leaverou.me Lea Verou

      Great advice there Robson!

    • http://bittersmann.de/ Gunnar Bittersmann

       Since there might be several associated dt elements, I suggest the selector ‘dd+dt’.

      • Robson Sobral

        Even simpler! Good idea!

    • http://twitter.com/ImINaBAR Agustín Amenabar

      Great I almost had it! only that I tried to do ‘n’ for newline… Thanks!
      In the end it came out as:
      dt, dd{display:inline;}
      What I couldn’t figure out was how to give it extra margin at the bottom without doing:
      which worked, but feels like a hack.

  • Anonymous

    if only they had kept the DI-element for grouping DT and DDs. (it would have had saner semantics, too, since you wouldn’t need to check the siblings to find out which DT a DD belongs to, but just if they share the same DI parent)

    • Sean Hogan

      There was a recent discussion about this on the HTML5 list, see http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2012-January/034315.html

      As you imply, it would have benefits for scripting as well, but there doesn’t seem to be any interest from the decision makers.

      Of course, you can already do this yourself with an LI-element. Just turn off list-markers for LI when a child of DL, like this:

      dl > li { list-style-type: none; }

      • http://bittersmann.de/ Gunnar Bittersmann

        The ignorance of the “decision makers” bothers me, too. BTW, is the plural form or the singular form more appropriate?

  • SelenIT

     Lea, sorry, but it seems that there is another drawback in this solution: we are unable to control the spacing between dt-dd pairs using margins, as we usually do :(

    I tried to workaround it by adding line-height and vertical-align: top to the pseudo-element, but it gave me the expected result only in Firefox. In IE9 (in both IE9 and IE8 modes) the line-height of the pseudo-element seems to affect the line following it, too.

    Also, I’m interested if the carriage return character (‘D’) is necessary here and, if yes, why? As CSS2.1 spec states, “UAs must recognize line feeds (U+000A) as newline characters” — and my experiments show, that at least for Firefox and IE9 (Win 7) just ‘A’ is sufficient…

    • http://leaverou.me Lea Verou

      You can control margins if you use display:inline-block; rather than just inline;.

      About the carriage return character, you could be right, but we need to test extensively, as different platforms and browsers can have different newline treatment. CRLF was the safe, bulletproof choice. But I hope you’re right and just LF is sufficient.

      • SelenIT

        But display: inline-block would produce completely different result if dd content becomes multiline, won’t it?

        And about newline characters, I’ve noticed that a single LF seems to be more bulletproof since CRLF seems to produce _two_ line breaks in IE instead of one (inserting an extra empty line between the text lines), although the spec states that “If no
        newline rules are specified for the document language, each carriage
        return (U+000D) and CRLF sequence (U+000D U+000A) in the document text
        is treated as single line feed character”…

        • http://leaverou.me Lea Verou

          You don’t need to set both as inline-block, only the dt would do. dts are almost always single line.

          Re: line feed. Odd, in my tests it was one newline in IE8. 

        • SelenIT

          Thanks a lot for the nice solution for the margin problem! I got the idea after some thinking on your previous hint.

          Re: Re: line feed. In this testcase: http://dabblet.com/result/gist/1904964/ebfff513cb78dd0d7c81744f18116c766740e22e — I clearly see different rendering of the two dls. IE 9.0.8112.16421, Win 7 Pro x64.

        • http://twitter.com/SelenIT2 SelenIT

          So does the testcase above render for me:

        • http://w3development.co.uk/ Andrew Pendlebury

          Like SelenIT, I too see two newlines in IE8 (and IE9) when “DA” is used as the value of content. “A” (as you have in your final example) is sufficient.

          With both the DT and DD displayed inline, white-space in the source now plays a part in the final rendering. For instance, if there is a newline between the DT and the DD in the source markup then they will be separated by a space when displayed in the page. Unfortunately, in IE8, if there is a newline in the source after the DD then there will be a space at the start of subsequent DTs which is undesirable. (…but not in IE9 and other browsers)

  • http://twitter.com/JasonNeel Jason Neel

    A question about a possible tweak to the markup: would it be acceptable to use an :after pseudo class on the dt to display the colon after the term being defined? I’m just thinking about how the display of the information may look with default styling if the CSS didn’t load. Not really a major issue, but something that crossed mind. Wasn’t sure if that would be an acceptable case for using pseudo classes or not. Thoughts?

    • http://leaverou.me Lea Verou

      Yes, I do that a lot. I added the colon here manually because I wanted to keep the CSS simple.

      • http://twitter.com/JasonNeel Jason Neel

        Execellent. That’s what I had figured. Good stuff.

  • http://twitter.com/psyw Максим Усачёв

    Lea, thank you very much for the best solution!

  • Pingback: Some links for light reading (29/2/12) | Max Design

  • http://twitter.com/dudleystorey Dudley Storey

    Lea, it occurs to me that this might also be a solution to laying out forms without extra markup: having labels and inputs on the same line, but each label starting on a fresh line. The only issue I can see is that as an inline tag the labels can’t be provided with widths, and everything else I’ve tried throws the alignment out… any thoughts?

    • http://leaverou.me Lea Verou

      Use display: inline-block, then you’ll be able to set a min-width on the labels ;)
      (you can of course use a width instead of min-width, but min-width is more defensive coding, it won’t break when your assumptions are not met)

      • http://twitter.com/dudleystorey Dudley Storey

        Yep, did that… the only problem is that you can’t use :after on the inputs, as they are replaced content. And rewriting this excellent trick as :before on the labels doesn’t appear to push the labels onto new lines when they are set to inline-block. (If the labels are set to inline using :before works fine, but of course then you can’t specify a width… grrr). 

        • http://leaverou.me Lea Verou

          Yeah, you’re right. Tough one. Trying to brainstorm, will update if I come up with something.

        • http://twitter.com/SelenIT2 SelenIT

          As a part of brainstorming (at least, for some special cases): what about something like http://dabblet.com/gist/1954158/? :)

          But I doubt that forms layout is the most appropriate use case for this technique. I came to the idea that probably the best use case for “inserting BRs from CSS” is structured data like postal addresses etc. (where it’s quite common to use real BRs for formatting). By applying this trick to the inline containers of certain logical parts of these data we can format them like  preformatted blocks only when it’s necessary, with no presentational markup (and even to do it in a “responsive” way using media queries). Lea, what do you think about using your brilliant invention this way?

        • http://leaverou.me Lea Verou

          I think your solution is brilliant. Well done!

        • http://twitter.com/dudleystorey Dudley Storey

          It’s very close, Selen… my only remaining issue would be that the layout is right-aligned. I’ll play around with it a little… thank you very much!

        • http://twitter.com/SelenIT2 SelenIT

          Dudley, sorry, I posted this example just as a brainstorming suggestion, not as a real solution. I think that for fixed-width container (as in my example) inserting line breaks is not necessary at all, just setting the widths for inputs and labels (with display:inline-block) should be enough. I tried to say that Lea’s technique isn’t the best approach for forms, but is super great as a flexible replacement for natural BRs.

  • Jon

    Typically I use 

    dt {clear: both;}
    dt, dd {padding:0;margin:0;display: block;float: left;margin-top: 0.5em;}

    but this solution is a little sweeter I’d say.

  • Pingback: Flexible multiline definition lists with 2 lines of CSS | AIT Themes

  • http://beben-koben.myopenid.com/ Beben Koben

    Sorry i’m comment here…
    In your great project http://leaverou.github.com/CSSS/
    I see not good in intro (first page) on bottom side “BY LEA VEROU”
    Like this  http://i42.tinypic.com/2cf2u51.jpg

    PADDING on #intro .attribution not good!
    Mozilla v.10

  • Dave Gööck

    i would use something like this because i don’t like to add some crude control characters to the page output (maybe a screenreader starts to beep ;) ).

    dd:after {
    content: ‘ ‘;
    display: block;
    height: 0;
    visibility: hidden;
    font-size: 0;
    }this is similar to a typical clearfix approach and does the same job and i think it should work at least on the same browsers like the sulution above.

    • http://leaverou.me Lea Verou

      I really doubt the screenreader starts to beep or anything. It’s basically equivalent to a br, or a “n” injected through JS, both of which are quite common. That would be a hell lot of beeps.

      • Dave Gööck

        sure a screenreader wouldn’t beep ;)

        oh, i see. the “a” stands for the hex-number A and therefore for the LF-control character. i was confused, because “a” is “alert” in other languages and “n” is the line feed. since my CSS-files are typically in UTF-8, i don’t have to use the NUMBER syntax to insert a unicode character.

        since your code is shorter than mine, its better ;) – maybe, i try your solution for a shorter clearfix mixin in my CSS framework. :)

  • http://twitter.com/gkatsanos George Katsanos

    I think at some point we need to have a long discussion on whether using before/after to add content – and in this case a newline character – in order to overcome layout problems is better than using non-semantic markup (presentetional markup) such as BR It has come to my attention that it’s a growing trend, and even if I use it, I often wonder what would Standardista’s and CSS spec authors say about it. (nota bene: any BR solution has the added benefit of being IE6/7 compatible.. (before/after pseudo selectors don’t work below IE8)

    • http://leaverou.me Lea Verou

      True, that’s a very valid concern that I also have myself. Especially since by spec, screen readers are expected to voice generated content.

      However, is it really presentational in this case? After all, the line breaks do have meaning (kinda). Also, if both approaches are equally correct, the CSS one saves on quite a lot of bytes (the bigger your lists, the bigger the savings) as they don’t have to be repeated for every item.

      Btw I asked some spec author friends on your behalf (David Baron and Tab Atkins) and told me they see nothing wrong with this approach although it’s not their preferred one. :)

    • maxw3st

      It’s time to get rid of IE6/7. We don’t use wheels with wooden spokes any more, why should we drag along obsolete browsers? The new ones are free.

  • Anonymous

    Had to solve someones bug  just now… Remembered your post… This just saved me from working even more overtime on a Friday evening. Hugs and kisses Lea!!!

  • Pingback: Гибкие многострочные списки определений при помощи двух строк CSS 2.1

  • http://www.darklightdusk.nl Luuk Lamers

    How about adding
    dt:after{content: ‘:’;}
    to bust the : out of the term?

  • maxw3st

    Great trick, I like it. Keeps the layout/style in the CSS and keeps the HTML looking semantic.

  • http://likelikeslike.com/ Jak Wings

    Good job!

  • Karen Menezes

    Lea, why does it not work with dt, dd’s display set to inline-block? I’ve used this hack in a completely different context and wondering why inline-block does not work. In order to get enough vertical margin between two lines, I had to resort to increasing the line-height on the inline element (since inline-block was not working)


    • http://lea.verou.me/ Lea Verou

      Because inline-block creates a separate box instead of multiple multiline boxes, so the line break is inside that box. Same behavior as appending a <br>

  • Martin Blase

    I encountered a bug with this approach in Webkit/Chrome when the dt/dd combination fit on one line — the end of the dd would overflow the container. The solution is to change ‘dd:after’ to ‘dt:before’ and add ‘dt:first-child:before { content: “”; }’

  • jkinley

    Hi, I read through this six times and I am still having trouble making a responsive definition list for a user bio. Seems the most semantic to me…maybe I am wrong. Based on the comments this is where I am. But I still see no way to make it so that multiple s can work without floats. Any ideas? Here is my code: http://cdpn.io/c/vwDmx