w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 649 forks source link

[css-pseudo-4] Enabling carousel design patterns in CSS #9745

Open flackr opened 8 months ago

flackr commented 8 months ago

Carousels are an often used design pattern on the web. They are used in a variety of contexts, from product listing pages to slideshow like content. OpenUI has explored a range of carousel designs, showing that the specific layout and appearance can vary dramatically. They are also provided by many frameworks as components, however implementing a carousel correctly is complicated and often results in inconsistent and sometimes inaccessible implementations.

There are a variety of problems being solved by carousels, which we believe could be provided by a set of CSS features. Developers could then combine these CSS features to create the various designs. CSS-only component libraries could be built to further simplify this process.

I'd like to share a strawman set of feature proposals that enable the creation of a variety of carousel patterns out of plain HTML lists using CSS. I've put up an explainer with polyfills and demos (e.g. see the full carousel) where you can see the individual features and how they can be combined to make a full fledged CSS carousel. I'd like to introduce the problem, features and get feedback on the overall direction and where to take the discussion.

Explainer: https://github.com/flackr/carousel/

I'm adding this under css-pseudo-4 for now, but likely the features here would span css-grid-2, css-scroll-snap-2, and possibly other specs.

yisibl commented 7 months ago

Does the existing design consider such a drag switching mode?

https://github.com/w3c/csswg-drafts/assets/2784308/b7af07df-77ea-4181-82ff-469c367bb5f0

flackr commented 6 months ago

Does the existing design consider such a drag switching mode?

That's a very neat effect! It's possible we could support something like this, but it would likely impose some limitations on the styling of the scroll marker area.

css-meeting-bot commented 6 months ago

The CSS Working Group just discussed [css-pseudo-4] Enabling carousel design patterns in CSS, and agreed to the following:

The full IRC log of that discussion <TabAtkins> flackr: i've been xploring how to help devs make better carousels, because they're very common
<TabAtkins> flackr: but people do it in all sorts of ways, and they're often inaccessible or just don't feel like native features
<TabAtkins> flackr: so i've been exploring what we can do to help that
<TabAtkins> flackr: [shows a demo site]
<TabAtkins> flackr: i think there's a small set of new features that would enable implementing the main set of carousel styles
<TabAtkins> flackr: wanna go thru these and discuss them in whole, then break them into independent issues
<TabAtkins> flackr: first is stylable fragmentation
<TabAtkins> flackr: ::fragment, probably border and other layout-inducing properties aren't allowed
<TabAtkins> flackr: important thing is alignment working
<TabAtkins> flackr: you'll notice the dev hasn't decided how many items fit on a page here, the UA has, so as you resize it changes
<TabAtkins> flackr: this also works in more complex cases, like arbitrarily flowing content between pages
<TabAtkins> flackr: a common pattern is you want content in your scroller tha you create nav entry points to
<TabAtkins> flackr: in here the HTML is a list of sections, and we have a new pseudo-element on the section called ::scroll-marker
<TabAtkins> flackr: this automatically flows into a size-contained area adjacent to the scroller
<TabAtkins> flackr: the "scroll markers area", i've put in a specific grid area to the side
<bkardell_> q+
<astearns> zakim, open queue
<Zakim> ok, astearns, the speaker queue is open
<TabAtkins> flackr: and given each a counter so it numbers the sections
<bkardell_> q+
<TabAtkins> flackr: this is probably the most advanced of the features, it requires the markers to be focused, and requires tab-panel navigation features so you can arrow between them
<TabAtkins> flackr: another feature is grid-flow, which is useful when you have content where you want to flow multiple elements into a single grid area, consecutively
<TabAtkins> flackr: this demo is a tabbed display. the tabs get flowed into a single grid area
<TabAtkins> flackr: Tab has a longer description, the short version is you can only flow into your grid parent, so it doesn't change the hierarchy of the DOM
<TabAtkins> flackr: and in this exapmle i had anchor links to cause them to scroll to the associated section
<TabAtkins> flackr: last, in many times you want to create affordances to cause scrolling, like buttons to scroll up or down
<TabAtkins> flackr: proposing we allow this via a pseudo so you don't have to change the semantics of your content
<TabAtkins> flackr: here yoru content is just a scroller, and there are pseudo-element buttons that cause scrolling up and down, and turn off autoamtically when you can't scroll
<TabAtkins> flackr: put all these together, and you see you get a nice little carousel
<TabAtkins> flackr: the nice thing is the actual semantic content is just a list of items
<florian> q+
<TabAtkins> flackr: styles are what makes it a rich scrolling carousel, which we should be able to make accessible to match your current input modality
<TabAtkins> flackr: I have one more example showing scroll markers showing thumbnails
<TabAtkins> flackr: Here using a variable for your image, and since the ::scroll-marker inherits from the element it's annotating, it can pick up that variable
<TabAtkins> flackr: In a future version where you can do attr() as url, we can pull the href directly
<TabAtkins> flackr: so that's a very quick walkthru about how we could turn a simple list of content into a usable carousel
<TabAtkins> flackr: i want to figure out what the best next steps are
<emilio> q+
<TabAtkins> flackr: figure out where to do the individual features, and do proposals?
<TabAtkins> flackr: is anything missing? hopefully my github explainer is sufficient
<TabAtkins> bkardell_: lots of positive things
<astearns> explainer: https://github.com/flackr/carousel/
<TabAtkins> bkardell_: this gets close to a bunch of things iv'e been interested in for a while
<TabAtkins> bkardell_: i have some issues to ask about, like identifying somethin in the DOM with a scroll marker, or some qs about grid-flow
<TabAtkins> bkardell_: but i woudln't expect everything to be crystal clear at this stage
<TabAtkins> bkardell_: I just like it
<florian> q?
<bramus> +1 to that
<TabAtkins> florian: i like it, one quick note on fragment pseudo
<TabAtkins> florian: we had an attempt at this, it was broader because it was also doing layout-affecting properties
<TabAtkins> florian: one note tho was the design wasn't fragment, it's ::nth-fragment() so you could style them differently
<TabAtkins> fantasai: I think it's a litle different
<TabAtkins> iank_: big thing we care about is getting scroll-snap-align to snap to columns rather than elements
<TabAtkins> fantasai: there's a ::column pseudo designed to help with that
<TabAtkins> flackr: there's a few things I want to improve about columns, and maybe make this vertical as well so it's kinda paged
<fantasai> Ah we never specced it, but see https://github.com/w3c/csswg-drafts/issues/6017
<TabAtkins> florian: my point wasn't about how to craete the fragments, the pseudo that lets you target them, if this ::fragment is differnt from the older ::nth-fragment, I'm not sure what the difference would be
<rachelandrew> https://www.w3.org/TR/css-overflow-4/#fragment-styling is the nth-fragment spec
<TabAtkins> florian: but we don't need to reoslve that now, this is good ideas, we might need to reconcile things
<TabAtkins> iank_: ::nth-fragment is strictly more complex
<TabAtkins> fantasai: i think you're not fragmenting the element, you're fragmenting the content into pages
<TabAtkins> fantasai: those fragments are anonymous
<TabAtkins> fantasai: in ::nth-fragment you take an element, tell it to fragment, and now you have three block boxes
<TabAtkins> emilio: how is that different?
<TabAtkins> iank_: the multicol is anonymous here
<TabAtkins> emilio: we have pseudo-elements targetting anonymous boxes of the element
<TabAtkins> iank_: right that's the ::column pseudo that fantasai is talking about
<nicole__> q+
<TabAtkins> florian: so my comment doesn't need resolution, and might be wrong since elika sounds right. we might need to coalesce in the future, but +1 for now
<TabAtkins> emilio: there's also a section in the explainer about inert, do we need to go thru that
<iank_> I suspect the `::column` pseudo would suffice here.
<TabAtkins> flackr: it's still an important feature - most single-item-at-a-time carousels want offscreen content to be inert
<TabAtkins> flackr: right now that's only thru script tho
<TabAtkins> flackr: if we want authors to be able to do this without needing script, we'll need css-controlled inertness
<TabAtkins> flackr: I have a very loose proposal on this
<TabAtkins> flackr: I think this could be useful for some popover cases too
<TabAtkins> emilio: the weird thing about inert is some things need to escape inertness
<TabAtkins> emilio: allowing CSS to change the semantics of that...
<TabAtkins> emilio: in a fullscreen context, letting the non-fullscreen not be inert if CSS overrides seems weird
<TabAtkins> emilio: another question, about scroll-markers
<TabAtkins> emilio: I'm not clear on how that works
<TabAtkins> emilio: the scrolling box generates the markers
<TabAtkins> flackr: no the marker is on the child elements, and they're auto-flowed into a special zone on the nearest scroller
<TabAtkins> iank_: in practice there will be a step after layout which will go explicitly insert these things into a size-contained area
<TabAtkins> emilio: so if you do *::scroll-marker, then everything generates markers that stash on the root scroller?
<TabAtkins> emilio: you'd need to add pseudo-element resolution once per element...
<TabAtkins> emilio: the overhead when you're not using it seems not ideal
<TabAtkins> emilio: just concerned about adding more ::before/after/marker
<TabAtkins> emilio: more unconditional pseudos can add overhead to pages that don't use the feature
<TabAtkins> flackr: I think we could completely skip them if there's not a scrollmarkers area
<TabAtkins> flackr: In my example there's a separate pseudo-element on the container that says it accepts the markers
<TabAtkins> emilio: right, but you'd do this.... i guess you could do all these after layout somehow, i guess that's what Ian is suggesting
<TabAtkins> emilio: it does mean you need to resolve the scroll-markers pseudo on all scrollers
<TabAtkins> emilio: and what triggers the :scroll-markers pseudo?
<TabAtkins> emilio: I don't see a 'content' property
<TabAtkins> flackr: that's a good question, i'd have to clarify
<TabAtkins> TabAtkins: I expect either a 'content' value, or a new property opting the scroller into it
<TabAtkins> emilio: yeah i think a property would make sense, so you don't need a second pass to find out what elements are supposed to generate a pseudo
<TabAtkins> emilio: i guess the ::scroll-marker pseudo is created for any element in the flat tree for which that scroller is its nearest scroll container?
<TabAtkins> flackr: yeah
<TabAtkins> emilio: ok, i think it's a bit complicated but it could be made to work if you don't force an extra style reoslution
<TabAtkins> emilio: if we could control this in a simpler way
<fantasai> TabAtkins: If both of these were generated only when a new property were set, would that solve the concern?
<TabAtkins> emilio: it would help yes
<TabAtkins> emilio: if people set it on the root tho you could still get a lot
<TabAtkins> TabAtkins: yeah but don't do bad things
<TabAtkins> emilio: people do tho ^_^
<TabAtkins> emilio: also a question about how this interacts with root scroller. maybe we just say it doesn't work?
<TabAtkins> flackr: yeah that might make sense. i could imagine it working, but we can see
<TabAtkins> nicole__: yeah like a slide deck. but this isn't trying to solve that, it's a more narrow use-cases
<TabAtkins> emilio: for elements not otherwise scrollable by the user, would that still work
<TabAtkins> emilio: the automatic scrolling buttons, like on an overflow:hidden
<TabAtkins> TabAtkins: you can link to things in a hidden overflow, or use scrollTo()
<TabAtkins> flackr: there are def carousels where devs make it so you can only scroll with the buttons, not otherwise. whether that's good or not is a decision we can make, maybe we don't generate the buttons unless there's a scrollbar
<TabAtkins> emilio: and similarly, more pseudo-element checks to see if any scroller needs to generate these scroller button pseudos
<TabAtkins> emilio: scrollbars already generate some overhead
<TabAtkins> emilio: but maybe similar solution to previous
<TabAtkins> emilio: otherwise tho seems workable, was a bit surprised to see multicol here
<TabAtkins> q+
<TabAtkins> emilio: feels a bit funky for something that isn't columns
<fantasai> TabAtkins: can't do carousels with multicol rn
<TabAtkins> nicole__: i think there's a bunch of stuff here that's hard for devs to do on their own
<TabAtkins> nicole__: when you think about a carousel it seems straightforward, but then responsive, and columns that can hide and show, and items can change their size...
<TabAtkins> nicole__: quite difficult calculations actually
<TabAtkins> nicole__: so anything we can do to support that would be interesting to consider
<TabAtkins> nicole__: last bit, expanding on what emilio said
<TabAtkins> nicole__: lots of use-cases for carousel in commerce. product details pages often have 6-8 carousels: previously looked at, paired products, images, etc
<TabAtkins> nicole__: it's probably good for the web to make commerce pages easier and better
<TabAtkins> fantasai: great exploation, couple of concerns
<TabAtkins> fantasai: for scroll markers, you're focusing on auto-generated stuff
<TabAtkins> fantasai: i think the first step, rather than auto-genning, shoudl be - what if the author builds a lot of elements, how can we make them act like scroll markers
<TabAtkins> fantasai: so my first step, rather than make a bunch of magic pseudos, is okay, we have a scroller, it has content,
<TabAtkins> fantasai: (like ToC in specs, wouldn't it be great to highlight the section you're in)
<TabAtkins> fantasai: so explore what we need to make that work
<TabAtkins> fantasai: and on pagination, before we dive too deeply into pagination buttons, how do we create a widget that has the correct behavior and can trigger it via js
<nicole__> q+
<TabAtkins> fantasai: if i made a button, how do i make it do the scrolls in straightforward way
<TabAtkins> fantasai: so my first suggestion is can we built up the affordances so someone can create the UI in the DOM, and then hook into this behavior in an easy way
<TabAtkins> fantasai: and then from there, can look into pseudo-elements because you don't always want content in the dom since it's presentational
<TabAtkins> fantasai: in terms of prior art, there was a previous proposal for overflow:paged which was implemented in opera
<TabAtkins> fantasai: never really made it because it didn't solve the problem of controls
<TabAtkins> fantasai: authors want to create controls in all kinds of ways, sometimes in the scroller, or outside
<TabAtkins> fantasai: miht not want the controls to be so tightly adjacent in the tree structure
<TabAtkins> fantasai: so autogen is nice, but we need to let authors do what they want
<TabAtkins> fantasai: and then when we generate pseudos, we'll have a good idea of what limitations to palce on them
<florian> q?
<TabAtkins> fantasai: wrt multicol vs dedicated overflow, latter would let you control whethe ryou want a dedicated pgup/pgdn
<TabAtkins> fantasai: but people do other transitions
<TabAtkins> fantasai: if you're flipping between cards in your carousel, is scrolling what you want? or do we need other ways to style the transition. are scroll animations enough?
<TabAtkins> fantasai: those are my thoughts. in terms of what to work on, i think (1) how do we want to create a pagination mode that has easy API for manipulating it
<TabAtkins> fantasai: and allows authors to easily build UI that will do the correct thing
<TabAtkins> fantasai: and on scroll marker side, how to have the behavior where it knows it's the active thing and scrolls to the correct position
<TabAtkins> fantasai: can we do that in a way so the interaction between them is more declarative and straightforward than it is now
<TabAtkins> fantasai: maybe a fully declarative binding would make things a lot easier
<TabAtkins> fantasai: and then from there the auto-generating part... it's a little too tied to grid at the moment
<bradk> q+
<TabAtkins> flackr: i don't think any of the stuff is tied to grid except grid-flow. you can create most bits without grid
<Zakim> fantasai, you wanted to comment on in-DOM scroll markers vs decorative
<TabAtkins> flackr: for dedicated elements, one thing that pseudos are hugely advantageous for is establihsing that link with what it does
<TabAtkins> flackr: a scroll button needs a way to refer to what scroller it's touching
<TabAtkins> flackr: which i'm open to, but we need ways to repeat it on the page so you don't need to generate unique identifiers
<dholbert> TabAtkins (IRC): I agree with fantasai (IRC) that whatever functionality we do here shouldn't be magically tied just to the pseudos
<dholbert> TabAtkins (IRC): need to have some way to invoke them from JS. Flackr mentioned getting the button from the scroller; that could be a property, or linked in the DOM
<dholbert> TabAtkins (IRC): having the pseudos being the only way to get the "magical" hookup is probably reasonable. we can play in that space and see if real DOM can also get a magical thing too
<dholbert> TabAtkins (IRC): flackr, you were talking about multicol for pagination in your examples. Is that the only pagination mechanic?
<dholbert> flackr (IRC): there was an idea of tying this to flex-wrap also. But there are many use-cases where the content is reflowing, and columns does everything that you want to there
<dholbert> flackr (IRC): I do call out in the explainer that columns doesn't address everything; e.g. you can't reserve some space to peak into space beyond current area
<dholbert> TabAtkins (IRC): so your big final demo, each section is filling one column, with a column-break after each one?
<dholbert> iank_ (IRC): expanding on the flex thing: you could imagine a flexbox row that wraps. Doesn't stack in the cross axis, but stacks in the main axis
<dholbert> iank_ (IRC): then, you'd need something like scroll-snap-align to target individual flex lines which don't generate fragments today
<dholbert> iank_ (IRC): that's *a* path, potentially
<dholbert> TabAtkins (IRC): in this example with 3 items visible at a time, what causes these 3 columns to be a page?
<dholbert> flackr (IRC): the fact that we've told them to flow them to columns. that creates an anonymous fragmented container
<dholbert> TabAtkins (IRC): I see `columns:1`; I'm confused about where the column fragmenting is happening
<dholbert> flackr (IRC): [brings up a simpler example with One | Two | Three]
<dholbert> florian (IRC): `columns:1` isn't "no columns". It's start with one column, and then autogenerate more
<dholbert> flackr (IRC): if I do `columns:2`, then it's fitting 2 per area and snapping
<dholbert> TabAtkins (IRC): the thing I was missing is that the 3 children are 3 inline-blocks filling a single column horizontally. Not individual columns
<flackr> Example is at https://flackr.github.io/carousel/examples/fragmentation/
<TabAtkins> nicole__: elika, sounded like starting from a version where the dev provides the markup, how would they know the number of markers?
<TabAtkins> fantasai: for some examples, the number of pages is dicated by content, not layout concerns. lots of carousels that are one per page
<TabAtkins> fantasai: we have a similar issue in specs - the ToC scrolls to the part of the spec. we don't have a binding in the other direction. when i scroll to a new section in the spec, or my carousel, i want the correct section/marker to get highlighted
<TabAtkins> fantasai: no connection back to the navigation
<TabAtkins> nicole__: like in teh bootstrap docs?
<TabAtkins> fantasai: yes, it's real common, but it's complicated
<TabAtkins> fantasai: so it would be nice to create this association, as long as you have elements that are in view
<TabAtkins> fantasai: so you could style the markers appropriately, and also make them clickable
<bkardell_> this is why I mentioned the element thing as desirable -- this is I think the same challenge with the tabs looking one I think, currently
<TabAtkins> fantasai: the case where you have something paginating based on how much content fits, then you might want page markers rather than content markers, we'd want to auto-gen those
<TabAtkins> fantasai: but on the way to that i want to make sure elements in the dom can work
<bramus> Note that the scrollspy pattern became easier to do thanks to scroll-driven animations.
<TabAtkins> fantasai: it doesn't require us to figure out nearly as much stuff before we can start helping authors
<TabAtkins> nicole__: I think there's a bit of danger becuase it's hard to say what's a carousel and waht isn't
<TabAtkins> nicole__: boundaries have blurred
<TabAtkins> nicole__: so figuring out which use-cases are in and which are out is good
<TabAtkins> fantasai: maybe, but i don't want to limit us to such a specialized use-case that you can't do more things with it
<TabAtkins> fantasai: building up pseudo-elements that have a very simple relationship to their originating element - kinda a pain in the ass but still relatively simple
<TabAtkins> fantasai: but doing more complicated things where they lay out in a way that's not directly associated with the alyout tree, that's a lot more complicated
<TabAtkins> nicole__: i sort of hate the way this works in form controls, with IDs and such where you need unique ids to hook them all together
<TabAtkins> fantasai: we've been doing bindings with scoped names in various things in CSS
<florian> q?
<TabAtkins> fantasai: but you might also want to pick up DOM relationships that are already there, using IDs or the like
<TabAtkins> fantasai: but i just want to see - you won't need nearly as much JS with a bunch of these abilities
<TabAtkins> fantasai: they can generate a few elements, and then just hook up the properties.
<TabAtkins> fantasai: JS is hard, if it's complex authors can screw up accessibility
<TabAtkins> fantasai: If we can give all the bheavior and they just have to provide markup, we're already way ahead
<TabAtkins> bradk: when i've heard about this, it reminded me of regions, especially the grid-flow
<TabAtkins> bradk: is this sorta a subset of that? or superior to that? have we considered synergy with those earlier ideas with regions?
<TabAtkins> bramus: even continue:fragments is sorta an alternative to using columsn
<fantasai> scribe+
<fantasai> s/bramus/bradk/
<dholbert> TabAtkins (IRC): I can answer about the grid-flow thing
<florian> q+
<dholbert> TabAtkins (IRC): for the grid-flow functionality, we're learning some of the lessons from regions
<dholbert> TabAtkins (IRC): being too open in what sort of movement you allow across DOM causes issues for layout and a11y
<dholbert> TabAtkins (IRC): having things reparented effectively in the DOM tree can be problematic for a11y tools
<dholbert> TabAtkins (IRC): for this reason, `grid-flow` in this proposal is very limited
<dholbert> TabAtkins (IRC): With this proposal, all the children maintain the same parent-child relationship that they had before. This doesn't change those relationships, very intentionally
<dholbert> TabAtkins (IRC): This is trying to avoid the issues that we ran into before
<dholbert> bradk (IRC): so looking at a more limited set of problems here?
<dholbert> TabAtkins (IRC): yup. Plus, since they're grid items, they'll be block formatting contexts, which are easier to lay out into a new container
<dholbert> TabAtkins (IRC): If we were piecing together non-BFC things from different places in the layout tree, there'd be much more complexity
<dholbert> TabAtkins (IRC): this avoids complexity, circularity
<dholbert> iank_ (IRC): the more we dived into use-cases here, the more we realized that stuff could just be solved by pseudo reparenting
<dholbert> florian (IRC): I think the situation with continue:fragment vs. multicol is similar in a way
<TabAtkins> florian: you might be able to do them multicol or continue;fragments, but multicol is so much simpler
<TabAtkins> florian: we already know how to do the multicol stuff
<TabAtkins> florian: and many times multicol is good enough for majority of cases anyway
<TabAtkins> florian: when we get to the point where we *do* need more, we can swap into that
<TabAtkins> bradk: yeah, was wondering if things like continue:fragments was intended to be a part of this
<TabAtkins> florian: I haven't looked deeply into this, but I believe that it should be psosible in the future to swap in a continue-fragments rather than multicol
<TabAtkins> flackr: yes
<TabAtkins> fantasai: rob, questions?
<TabAtkins> flackr: feedback has been great. we outlined a few things it would be nice to work on first, maybe scroll-markers first. what spec would it belong in?
<TabAtkins> fantasai: the pseudo-elements, or the scrolling binding?
<TabAtkins> flackr: both/either
<TabAtkins> fantasai: hmm, unsure. maybe scroll-snap is closest?
<TabAtkins> TabAtkins: yeah scroll snap 2 has some similar ideas
<TabAtkins> TabAtkins: but chrome has an impl trying to stabilize a few of these ideas
<TabAtkins> fantasai: overflow 5?
<TabAtkins> florian: yeah that might work
<TabAtkins> fantasai: it would let us centralize all the overflowing things together
<TabAtkins> fantasai: so i think proposal is to start overflow 5 draft, to explore paginating overflow, and scroll markers, and move appendix stuff into it
<TabAtkins> florian: Rob as a co-editor?
<TabAtkins> fantasai: yes definitely
<TabAtkins> fantasai: editors would be me, florian, rob
<TabAtkins> fantasai: comments? objections?
<TabAtkins> RESOLVED: Start overflow-5 draft, editors elika, florian, rob, move the fragmentation appendix from overflow-4 into it, work on addressing paginated overflow and scroll markers
<TabAtkins> TabAtkins: do we want to resolve about starting on ::grid-flow?
<TabAtkins> fantasai: maybe a separate discussion