- From: Tab Atkins Jr. <jackalmage@gmail.com>
- Date: Mon, 20 Apr 2009 09:44:04 -0500
- To: www-style <www-style@w3.org>
Greetings, list. In the recent updated draft of Template Layout, the section on tabbed layouts was removed. This was a good choice, as there had been very little discussion on how to approach this problem. I hope to correct that in this thread. I will discuss existing tabbed layouts implemented in js, explain an advanced example utilizing js-based tabbed display, show a working example of a tabbed layout in pure CSS (it's a hack, but it works), dissect where I believe the tabbed layout section in the Advanced Layout Module went wrong, and finally outline what I think the best approach to this would be. The Problem =========== Tabbed layouts are reasonably common in heavily js-driven sites. They allow you to present small bits of information, and offer further information if requested without a server round-trip. I personally use a tabbed layout in *all* of the internal sites at my company, to organize information without producing a ton of pages. Because it doesn't cause a proper server request, it's also very useful for when you are presenting a form, but want to make large amounts of information available as well In general terms, a tabbed layout consists of a number of 'cards' organized into a 'stack', such that only one card from a given stack can be displayed at a time. There are also 'tabs', which activate particular cards, causing them to be displayed (and hiding the currently-displayed card in that stack, if it's different). When phrased in this way, the tabbed layout is even more prevalent than one would immediately think, as the popular web 2.0 "accordion" construct also matches this definition. The only difference is that in a traditional tabbed layout the tabs and cards are grouped separately, while in a traditional accordion the cards are interwoven with the tabs. As well, a tabbed layout requires the ability to distinguish an 'active' tab (corresponding to a currently displayed card) from an inactive tab. Existing JS Solutions ===================== Tabbed layouts are popular enough that they were one of the earliest jQuery plugins produced, and are now part of the official UI plugin stack. (Accordion is as well.) In the jQuery tabbed layout, the focus is on the tabs. Cards are simply undistinguished elements on the page, which must have #ids. The tabs, however, have a very particular structure. They must be presented as a <ul>, with each <li> containing an <a> hash-linking to a particular card. The collection of <li>s here defines the stack. When activated and pointed at the tabs' <ul>, the javascript hunts for the links, associates them with the linked elements, and applies appropriate classes to everything (tabs-container, active-tab, inactive-tab, active-card, inactive-card, etc.) to allow you to style appropriately. It then attaches a few click event listeners to the tabs so that it can swap the active/inactive classes when appropriate. This ends up being extremely nice and flexible, and degrades well. In the absence of javascript, all the content is displayed, and you have a list of links to particular sections for quick access. The tabs and cards can be well-separated from each other in the markup and display(this has been very useful to me before), and everything is fully stylable with standard CSS techniques. It also has a slight weakness. Since the stack is defined by the tabs, not the cards, it's possible for a single card to be a part of multiple stacks, which can produce confusing behavior. However, it also allows multiple tabs to correspond to a single tab, which *has* been useful to me in the past. Advanced Example of JS-based tabbed layout ========================================== At http://www.xanthir.com/xyz-security-systems/floorplan.php you'll see a page from a school project I did last semester. I use tabbed layout in a complex way to achieve the desired display behavior here. First, the floor display is a rather straightforward tabbed display. Two tabs, two cards, and the tabs and cards are next to each other in the layout (though not in the markup - this is purely for display purposes). More complex is the device display. First, the list on the left constitutes tabs corresponding to the individual device setup forms on the right. Note that the tabs and the cards are separated in the markup, and the tabs themselves are somewhat separated from each other (placed in two separate lists). More interesting are the devices on the central floorplan, which are *also* tabs for the device setup forms on the left. The two sets of tabs interact appropriately, as they cover the exact same set of cards. Tabbed Layout in Pure CSS ========================= At http://www.xanthir.com/css-modal.html I have implemented a tabbed display in pure CSS. The cards have class="card", and I use the :target pseudoclass to display the active card. I then employ the ingenious hidden :checked hack (I totally can't remember who introduced this originally) to style the active tab - the tab, in addition to being a link (to trigger :target behavior) is also a <label> corresponding to a hidden radio button immediately preceding it, so I can use a ":checked + * a" selector to target the active tab. This example is a horrible hack, and has several weaknesses (you have to use non-semantic <input>s to style the tab, you can't create multiple 'stacks', other hash-links (like the modal example also on that page) interfere), but it demonstrates that the essential powers needed to implement this are appropriately part of CSS. Problems in Advanced Layout Module tabbed layout ================================================ The draft is no longer up, so I'll summarize how the proposal worked. display:card elements were placed into a display:stack container, defining their stack explicitly in the markup. An element within the card was given display:tab. This causes it to be pulled out of the card's display, and used to fill the automatically generated tab display docked on one side of the stack. There are several weaknesses with this draft: 1. It was a slight abuse of display (display:stack and display:card elements were treated as display:block by default). 2. It forced the designer to put the card's stack relationship explicitly in the markup. In many basic tabbed displays this is fine, but there can be legitimate reasons to *not* do this. 3. The tabs were extremely limited. They must come from the card's descendants, they must be next to the card stack, and their display is essentially browser chrome, limiting the author's ability to change the style of the tabs. 4. Tabs and cards are forced into a one-to-one relationship, preventing an author from creating complex layouts like I outlined above in my example. 5. Accordions are completely impossible, even though they are functionally identical to a tabbed layout. These weaknesses are largely a result of defensible choices, of course. This approach makes it easy to take existing markup and transform it into a tabbed layout. However, it severely restricts an author's options when they know that a tabbed layout is intended from the beginning. Both this and my proposal (below) offer good fallback behavior in the absence of CSS. A Proposal for a Tabbed Layout ============================== My proposal takes the flexibility of JS-based tabbed layouts and marries it to the simplicity and rigor that CSS can bring. It requires only a single new property and a handful of pseudoclasses. No pseudoelements or new values for existing properties are needed. It revolves around the use of hash-links and ids, but without leaning on the :target pseudoclass and with significant intelligence to help in styling. New Property: card-stack ------------------------ Values: none | <string> Initial: none Applies to: all elements Inherited: no The card-stack property identifies an element as a card in a tabbed layout, and defines the stack it belongs to. Only a single card (the 'active' card) from a given stack is displayed at a time - the rest of the cards in the stack are treated as display:none. A card is made 'active' by either it or one of its descendants being expressly targetted by a hash-link. Once a card is 'active', it remains 'active' until another card in the same stack is made 'active'. Cards in other stacks can be made 'active' without affecting cards in any other stack. Any card with a string value for card-stack matches the pseudoclass :card. Currently active cards also match the :active-card pseudoclass. ((Is an :inactive-card pseudoclass desirable? It's not necessary, but it may make things slightly easier - you won't have to set inactive properties with :card and then explicitly override them with :active-card. It would also make defining the UA defaults easier - just say ":inactive-card{display:none !important;}". Alternately, drop :card and just use :active-card and :inactive-card.)) Any link which is targetting a card on the page is considered a 'tab', and will match the :tab pseudoclass. A link targetting a currently active tab will also match the :active-tab pseudoclass. ((Again, do we want an :inactive-tab pseudoclass instead of/in addition to the :tab pseudoclass?)) Links targetting descendants of a card will activate the card when followed, but do not count as tabs themselves. Questions/Issues ---------------- Currently, *all* cards start out inactive when you enter a page (unless the page had a hash, of course). Most JS-based tabbed layouts will automatically activate the first card (with 'first' defined in some consistent manner). Should we adopt this? Should there be a control for this? How would it work? Once a card has been activated from a stack, there's no way currently to close all cards from that stack - you'll always have one open. Most js-based tabbed layouts allow you to click an active tab to close the card. This seems reasonable to me, but it would require a slight complexification of the card handling. Should I expand the wording concerning activation and tab-selection to allow other means of targetting (such as XPointer)? I don't see any particular reason *not* to. I still liked that the original draft allowed you to easily transform existing markup into a tabbed layout. Can anyone see an easy way to merge the two solutions? ~TJ
Received on Monday, 20 April 2009 14:44:48 UTC