Re: Tabbed Interfaces in CSS

We are leaking from Tabbed Layout into Advanced User Interface,
Behavioural Extensions to CSS, CSS Object Model, Selectors Level 4...

2009/4/22 Brad Kemper <brad.kemper@gmail.com>:
>>
> Slightly modified from the first time I sent it:
>
> fieldset { radio-group:mytabs; appearance:tab-card; display:inline-block;
> position:absolute; left:0; }
> fieldset:not(:checked) { appearance:none; z-index:-1; }
> fieldset:not(:checked) * { display:none; }
> fieldset:not(:checked) legend { appearance: tab-top-back;
> display:inline-block; }
> fieldset:checked legend { appearance: tab-top-front; display:inline-block; }
>
> See below for an explanation of each line and how it would affect the
> rendering...
>
>> and what would be the expected
>> rendering?  (I ask for this again just to ensure that I am absolutely
>> comprehending your proposal properly.  I had the advantage of starting
>> a thread with a formal proposal; yours developed somewhat organically
>> within the thread.)  Make sure that there's a way to distinguish the
>> active tab as well.
>
> fieldset { radio-group:mytabs; appearance:tab-card; display:inline-block;
> position:absolute; left:0; }
> This first rule says that both fieldsets in your example are part of the
> same radio group, given the identifier of "mytabs", and that they should
> have the OS appearance for the display area of a tabbed display (an author
> could specify their own style instead of using appearance if he wished). By
> putting these into a radio group, the UA should track the "checked" state of
> each one according to radio-button standards, not according to check-box
> standards. By default, the first fieldset ("foo") has a "checked" state, and
> any others ("bar") do not. The absolute positioning and the inline-block
> display makes all the fieldsets appear at the same x/y coordinates.

Ehm... the fact is that the tracking of :checked state (according to
Selectors Level 3) depends only on the markup language. And neither
HTML4 nor HTML5 (nor XHTML1.0 / 1.1 / 2) define :checked for fieldset
(actually, for everything outside input[type="checkbox"] and
input[type="radio"]). So you need to define how the element is
checked.

> fieldset:not(:checked) { appearance:none; z-index:-1; }
>
> This second rule says that any of these fieldsets that do not have a checked
> state would not have any OS appearance (including no shadows or borders in
> OS X), and that they should be lower in the stacking order than fieldsets
> with a checked state (an author might also want to explicitly remove any
> borders, padding, etc. here that might be part of the default fieldset
> styling).

I don't understand why appearance:none;

> fieldset:not(:checked) * { display:none; }
>
> This third rule says that those non-checked fieldsets should not display any
> of their contents. Combined with the previous rule, it means that only the
> checked fieldset will be visible (but without using display or visibility on
> the fieldset itself).

I'd rather fieldset:not(:checked) > :not(legend) { display:none; }
And notice that this does not hide anonymous boxes (ie text content
inside the fieldset)

> fieldset:not(:checked) legend { appearance: tab-top-back;
> display:inline-block; }
>
> These make an exception to the rule right before it: it allows the legend
> element to be displayed (so far, only the unchecked version) and gives it an
> OS appearance of an unselected ("back") tab of the variety that you see
> along the top of a tabbed display. This is more specific than just
> "appearance:tab", which does not seem to be enough to describe the different
> states or positional types for tabs. Maybe there would be a better way to
> indicate the appearance state, or maybe the UA would be able to figure this
> out on its own as part of appearance magic, but I am trying to reduce the
> reliance on magic. The author could also use non-appearance-based styling
> here to indicate non-selected-tab instead, if he wished.

Why clicking the legend should change the :checked state of its parent
(that is not an input, btw)?
This represent the behavior (event handling). You need some way to
attach behavior (BECSS) and to change the pseudo-classes that apply to
an element (CSSOM).
"appearance", per Basic User Interface, includes only "color", "font",
"cursor", "margin", "padding", "border", "outline" and
"text-decoration": no "binding", no "xml-events", no "onclick", no
"behavior", no "left", not even "width", "height" or "display".
But, if we can define a value for "appearance", we can safely define a
set of keywords or UAs native URIs for "binding". You don't even need
"radio-group" (formerly "toggle-group").

> fieldset:checked legend { appearance: tab-top-front; display:inline-block;}
>
> This is the actual tab for the tab that's in front. More appearance (or
> other author-supplied styling) to indicate that it should look like a
> clicked-on tab that goes on top of the box (not sides or bottom), and make
> it display.

I would like this to be "appearance:active-tab" and
"appearance:inactive-tab". Btw, we have "appearance:tab" in the Basic
User Interface.

> Now so far all the tabs are one above each other, instead of appearing in a
> row. So the appearance would do some magic to set the 'left' of each
> according to the intrinsic width of the one before it. Alternately, the
> author who did not want to use appearance would have to rely on a different
> spec for floating or moving the item to a different named location just
> above the fieldsets. OR, less exotically but also less satisfyingly, the
> author could just give each tab a set width and then set the left position
> of each based on the width of the one before it, selecting each one via
> ':nth-of-type()' and LEGEND. This is the one place I have where some magic
> is desirable, but there are other ways around it (such as using labels in a
> way similar to my old pure-CSS example, if you have the option of editing
> the source code).

It definitely does not belong to appearance having magic on the
interpretation of "left" (that is not auto, btw), considering that
position:absolute is not required (legend is not positioned in your
example) and I may want the "look" of a native tab, while having the
standard CSS layout (expressed by the CSS property "display").
This means that you need position:slot() / float:to() / move-to: that
is you need Template Layout and/or Named Flows.
Or, on the other side, you need to specially "display" the tab.

>> Also, is there a way in your magic generation to handle both the tab
>> and accordion cases?  The only distinction between the two is that the
>> former collects the tabs together, while the latter distributes them
>> with their card.
>
> Accordion would be even easier. Imagine a div for each section of the
> accordion, with a single H3 as the first child of each section:
>
> div { radio-group:myaccordians; overflow:hidden; font-size:18px;
> margin-top:2px; }
> div:not(:checked) { height:1em; }
> h3 { height:1em; background-color:black; color:white; }
>
> <div>
> <h3>Click me to open or close this div</h3>
> <p>A bunch of text, etc.</p>
> </div>
> <div>
> <h3>Me too!</h3>
> <p>bla, bla, bla</p>
> <p>More stuff to be hidden</p>
> </div>
> <div>
> <h3>Get the idea</h3>
> <p>Hidable stuff for the third part</p>
> </div>

What if the h3 has a greater line-heigth? Or generates multiple lines?
Or contains inline-blocks?
You'd rather use "div:not(:checked) > :not(h3) { display:none; }"
(with the problem that you need explicit blocks)
And again, clicking on h3 does not check the div. You need to attach
some event handling.

>> >>> > Links are only one thing at a time, so using
>> >>
>> >> >> > them for tab-panel-showing would not allow a page with more than
>> >> >> > one
>> >> >> > set
>> >> >> > of
>> >> >> > tabs to have a particular tab in the front.
>> >> >>
>> >> >> Hm?  I addressed this in my proposal.  Cards are *activated* by
>> >> >> links,
>> >> >> but they don't depend on links to *stay* activated.  The CSS engine
>> >> >> would keep track of which card is active in each stack.
>> >> >
>> >> > Sorry, I guess I missed that you were adding more magic there. But
>> >> > then
>> >> > there is no way to make one of the tabs other than the first as a
>> >> > default,
>> >> > other than by changing the URL to the page? And even then, only one
>> >> > of
>> >> > the
>> >> > sets of tabs could have a default?
>> >>
>> >> Right now, yes.  Is it important that a different tab than the first
>> >> show as a default?
>> >
>> > Sometimes. Yes. Absolutely. Not as important, perhaps, but certainly
>> > desirable.
>>
>>
>> I suspect less often than you would think, especially when you
>> consider that you can just make the default card be first in your
>> markup.  The situations where you want a card to come first in the
>> markup but *not* be the default displayed card would be very rare, I
>> think.
>
> I can easily imagine cases where some cookie remembers the last tab you have
> open so that it will be open the next time. Or where you want to link to a
> certain configuration of tabs showing. First on left would be most cases,
> sure, but there would be enough other cases for it to be missed if it wasn't
> an option.

This obviously depends on the behavior associated with the tab (or
really the stack). Assuming that it will be implemented natively (I'm
talking of "about:" bindings, I don't require ES) the browser can
remember the last click with no problems.

>>
>> When this *does* happen to come up, my proposal would allow
>> you to do this by targetting the card in the link.  The chances that
>> you need *two* things to start with a card other than the first open
>> by default are, I believe, *extremely* rare.
>
> If you are using the tabs for navigation then that's no problem. If you are
> doing something more complex, like emulating several open windows that might
> have tabs in them, then it'd be a shame to not have that option.

Sorry, I don't understand what you want here.

>>
>> (Nevertheless, there exists possibilities for handling this within my
>> proposal.  If it becomes necessary to customize the behavior of a
>> stack (such as by altering the default displayed card), we can
>> introduce a ::card-stack() pseudoelement which is nothing more than a
>> centralized place to put properties that apply to a particular stack.)
>>
>> >> (I think your proposal would do this by making @checked a global
>> >> attribute, right?  You'd need to change HTML for that.)
>> >
>> > See my last e-mail in response to Giovanni. I'm not above asking for a
>> > small
>> > change to HTML, but I don't require it.
>>
>> Gotcha.

This is the CSS WG. We should not depend on a particular markup
language for certain effects (we all love XML, don't we?)

>> >> >> > And wouldn't it also cause the
>> >> >> > page to scroll to the tab, whether that was wanted or not?
>> >> >>
>> >> >> Yes it would.  This is currently a problem, but Giovanni suggested a
>> >> >> solution in the link behaviors spec.
>> >> >
>> >> > I didn't see that, and still can't find it in skimming through what
>> >> > he
>> >> > posted. Another dependency on a different spec?
>> >>
>> >> "Another" dependency?  I don't believe I currently rely on any other
>> >> spec.  And the dependency only exists if (1) the author decides that
>> >> the scrolling behavior is inappropriate, and (2) we don't just fold
>> >> the anti-scrolling magic directly into this proposal.
>> >
>> > One propopsal for tabs requires display-roles and display-models, which
>> > is a
>> > big can of worms.
>>
>> Not mine, though, so that dependency doesn't count against me.  ^_^
>
> Yes I know that, but you did say you preferred it to my solution.

Different display "modes" (role/model combinations) can ensure the
level of magic that automatic tab repositioning / reordering requires.
"appearance" cannot.

> [...]

Giovanni

Received on Wednesday, 22 April 2009 13:19:17 UTC