CSS challenge

You, dear reader, are almost certainly one of the most talented CSS experts on the planet. No, don’t blush; everybody says it.

So here’s a challenge for you. I have a defintion list. It’s a glossary—a collection of terms and definitions. Semantically, therefore, it’s indisputably correctly marked up as dt and one or more dds, all wrapped in a single dl. (It’s not a naughty dl in a form or any misuse like that).

I want to style it in a way that’s very common for glossaries—term on the left, and all the different definitions on the right. Each new term starts a new “row” for want of a better word. It needs to be robust; some terms are longer than their definitions; some terms have multiple definitions.

I can’t do it.

If I add a non-semantic (and invalid) div around each term and its definition(s), then float:left; clear:left;, it works perfectly. But without that meaningless extra element, it looks minging. (Colours and the classes used to add those are just for illustration.)

Whichever way I try (absolute positioning, using display:table-*, inserting generated content and floating and clearing that), I can’t get it to look as I want it.

It’s obviously a common problem. The HTML 5 group declined to add a di (definition item) grouping element to solve this, rightly saying

This is a styling problem and should be fixed in CSS. There’s no reason to add a grouping element to HTML, as the semantics are already unambiguous.

So, dear reader: can you do it? There’s free entry to standards.next and a snog with tongues for the lucky winner.

Rules: you can’t change the markup. This would be easy to do with multiple dls, but it isn’t a series of lists: it’s one list of multiple terms. And, yes, it could be marked up as a table, but it isn’t a table, it’s a list of definitions and we all know that tables for layout is evil.

(This isn’t me just being lazy; Molly is attending the CSS Working Group’s face-to-face PARTAAAY!! meeting, and we were discussing things to bring up there.)

27 Responses to “ CSS challenge ”

Comment by Richard Conyard

Hopefully this will do you the trick (if I understand what you’re after):

* { margin: 0;}
body { width: 800; margin-left: auto; margin-right: auto; }
dt { clear: both; float: left; display: inline; width: 49%; border: 1px solid #C00; }
dd { float: right; display: inline; width: 50%; border: 1px solid #00C; }
dd.last { clear: both; }

place the last class on the last definition:

term
def
def
def
def

Has a few probs in IE which will need ironing out, but FF is fine. Now back to C# and away from the CSS diversion.

Comment by Jeff L

I don’t think it’s QUITE the effect you want, but you might be ok with it.

dd:after {
content: “.”;
display: block;
height: 0;
clear: both;
visibility: hidden;
}

Comment by Cole

Do you envisage a scenario with multiple terms per definition?
If not, then try floating your terms left and your definitions right with a clear:both on your terms (would need some custom – CC declared – for IE).
Otherwise, reckon some javascript jiggerpokery would be required.

Comment by Cal Wilson

OK I’m going to bite the bullet and provide the following test case: http://oksushi.com/test/dl.html

We just need to float the dt left and give it a width and clear: left, apply a margin-left of equal or greater width to the dd and then apply the clearfix trick to fix the issue with long dt and short dl.

Comment by James Hart

Try this:

dl
{
list-style-type: none;
}
dt
{
width: 17%;
padding: 0.5em;
float: left;
clear: left;
margin: 0;
margin-right: -1000000px;
margin-bottom: 1em;
}
dd
{
float: left;
margin-left: 20%;
width: 70%;
margin-bottom: 1em;
padding: 0.5em;
}

(sadly it’s proportional width – not sure I can do it with a fixed column)

Comment by Robert O'Rourke

Hi Bruce, the following works in everything but IE6 (I think).

dl {list-style-type:none; margin-left: 16em; }
dt {width:13em;padding:0.5em;float:left;clear:left;margin:0 0 1em -16em;
font-weight:bold;color:#686663; color:red; border-bottom:1px solid green;}
dd {float:left;width:100%;margin: 0 0 1em;padding:.5em 0; color:blue;
border-bottom:1px dashed blue; }
.first {background-color:yellow;}
.second {background-color:pink;}
.third {background-color:orange;}

Comment by Scott

Haven’t had the chance yet to try out others’ solutions, but came up with this (seems to work in Safari and FF3, don’t know about IE). I’m setting a fixed pixel width for the dt “column” – in this case 290px with a 10px gutter. Em widths may work also.

dl {
margin-left: 300px;
}
dt {
font-weight: bold;
float: left;
width: 290px;
margin-left: -300px;
clear: left;
}
dd {
float: left;
width: 100%;
display: block;
}

Comment by David

Humph. Spent the last 30 minutes or so getting something to work, only to find Scott, Robert and James got there with a very similar idea to mine …

Anyway, here’s what I came up with:

* {
margin: 0;
padding: 0;
}

dl {
list-style-type:none;
}

dt {
width: 28%;
padding: 5px;
margin: 3% -30% 0 0;

background-color: #ddd;
color: #333;
border-bottom: 1px solid #333;
text-align: left;

float: left;
clear: left;
}

dd {
width: 65%;
margin: 3% 0 0 30%;
float: left;

border-bottom: 1px solid #eee;
}

Comment by joolss

Here is one solution, works in FF3+, Saf3+, IE6+: http://www.johanolsson.net/projects/test_dl.html

dl {
background: red;
overflow: hidden;
zoom: 1;
position: relative;
line-height: 1.4em;
}

dt {
background: orange;
float: left;
width: 50%;
position: relative;
top: .4em; /* adjust to get spacing between term/definition “items” */
z-index: 100;
margin-bottom: -1em; /* adjust to get spacing between term/definition “items” */
}

dd {
background: yellow;
display: block;
width: 50%;
position: relative;
padding-left: 50%;
margin: 0;
clear: left;
}

This allows for more than one definition per term.

(if someone else has posted something similar, sorry =) )

Comment by Jeff L

Robert’s solution doesn’t work in IE, but adding a hack (is that allowed) seems to allow it to function in IE:


dl {
list-style-type:none;
margin-left: 16em;
}
dt {
border-bottom:1px solid green;
clear:left;
color:#686663;
float:left;
font-weight:bold;
margin:0 0 1em -16em;
padding:0.5em;
width:13em;
}
dd {
border-bottom:1px dashed blue;
color:blue;
float:left;
*float: none;
margin: 0 0 1em;
padding:.5em 0;
width:100%;
}

Comment by Nicolas Gallagher

Hi Bruce,

Using your markup I applied these styles to get the desired effect. Main difference is that for standards compliant browsers I had to add a set width to the dl and then float the dd’s.

Hopefully this meets your requirements

—————

dl {width:900px;}
dt {clear:both; float:left; width:260px; padding:10px; margin:0 0 2em; font-weight:bold; color:#686663;}
dd {clear:right; float:right; width:570px; padding:10px; margin:0 0 2em;}

.first {background-color:yellow;}
.second {background-color:pink;}
.third {background-color:orange;}

** And these styles go in some < IE8 only conditional.

dd {float:none; clear:none; margin:0 0 2em 300px;}

—————

Comment by James Hart

@Cal Wilson – your test page omits the scenario with a long term with multiple shorter definitions (Bruce’s second – pink – term in his test page). Your second and subsequent definitions will be pushed down the page to below the end of the term. The negative margin float solutions keep the definitions stacked up alongside the term.

Comment by Nicolas Gallagher

Hopefully this does the trick without setting a width on the dl and works in IE6+

dl {padding-left:300px;}
dt {clear:left; float:left; width:260px; padding:10px; margin:0 0 2em -300px; font-weight:bold; color:#686663;}
dd {float:left; width:100%; padding:10px 0; margin:0 0 2em;}

.first {background-color:yellow;}
.second {background-color:pink;}
.third {background-color:orange;}

/* show the styles below to lte IE7 */

dl {padding-left:0;}
dt {display:inline; clear:both; float:left; margin-left:0;}
dd {clear:none; float:none; width:auto; margin-left:300px;}

Comment by Alexis Deveria

Just for fun, I thought I’d try it using the CSS3 Template Layout Module:
http://a.deveria.com/csstpl/definition-lists-tlm.html

Yes, I know this is probably cheating since it uses JavaScript to parse the CSS, but the markup is preserved and the output appears as desired. Hey, it worked for Kirk. 🙂

Also there’s the issue that this solution works for this example, but you’d have to adjust it for additional definitions.

Comment by Chris Miller

Hello,

We’ve been using DL’s to layout forms (user-definitions lists) on Blinkbox for some time. You can see this in use on our registration page:

http://www.blinkbox.com/register

What follows is a cleaned up version for your general consumption.

Cheers
Chris

dl
{
margin: 0;
padding: 0;
}
dt
{
clear: left;
float: left;
text-align: right;
margin: 3px 0;
width: 12em;
}
dd
{
margin: 0 5px 5px;
padding-left: 13em;
}
dd:after /* Standard clearfix hack */
{
content: “.”;
display: block;
height: 0;
clear: both;
visibility: hidden;
overflow: hidden;
}
dd.wide
{
clear: left;
padding: 0 1em !important;
}

Comment by Bruce

I’m flabbergasted by the responses; thanks all.

18 months ago, I drove myself sane, tinkering with floats and display:table-* properties, but never did the lateral mental leap of leaving a large margin/padding and using negative margins to move the definition terms into it.

You ALL win free entry to standards.next, and can claim your snog when you next see me.

I quite enjoyed that challenge. Up for another one next week?

Comment by Robert O'Rourke

Could be fun 🙂

CSS puzzles like this one are what got me hooked in the first place. Nice to take a break from the humdrum and have a go at something like this. Always good to see how others approach it too.

More!

Comment by Michael Kozakewich

Oh, I used a negative margin on my site to shove even-width form controls to the right of my odd-width labels, which let me eschew any sort of tabular layout.

It really does feel naughty, even though it’s compliant XHTML 1.0 Strict and CSS2.1.

Leave a Reply

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> . To display code, manually escape it.