Tag: sticky

A table with both a sticky header and a sticky first column

We’ve covered that individual <table> cells, <th> and <td> can be position: sticky. It’s pretty easy to make the header of a table stick to the top of the screen while scrolling through a bunch or rows (like this demo).

But stickiness isn’t just for the top of the screen, you can stick things in any scroll direction (horizontal is just as fun). In fact, we can have multiple sticky elements stuck in different directions inside the same element, and even single elements that are stuck in multiple directions.

Here’s a video example of a table that sticks both the header and first column:

Why would you do that? Specifically for tabular data where cross-referencing is the point. In this table (which represents, of course, the scoring baseball game where somehow 20 teams are all playing each other at once because that’s how baseball works), it “makes sense” that you wouldn’t want the team name or the inning number to scroll away, as you’d lose context of what you’re looking at.

Not all tables need to be bi-directionally cross-referenceable. A lot of tables can smash rows into blocks on small screens for a better small-screen experience.

The “trick” at play here is partially the position: sticky; usage, but moreso to me, how you have to handle overlapping elements. A table cell that is sticky needs to have a background, because otherwise we’ll see overlapping content. It also needs proper z-index handling so that when it sticks in place, it’ll be on top of what it is supposed to be on top of. This feels like the trickiest part:

  • Make sure the tbody>th cells are above regular table cells, so they stay on top during a horizontal scroll.
  • Make sure the thead>th cells are above those, for vertical scrolling.
  • Make sure the thead>th:first-child cell is the very highest, as it needs to be above the body cells and it’s sibling headers again for horizontal scrolling.

A bit of a dance, but it’s doable.

High five to Cameron Clark who emailed me demoed this and showed me how cool it is. And indeed, Cameron, it is cool. When I shared that around, Estelle Weyl showed me a demo she made several years ago. That feels about right, Estelle is always a couple of years ahead of me.


The post A table with both a sticky header and a sticky first column appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,

How to Get Sticky and Full-Bleed Elements to Play Well Together

I had a unique requirement the other day: to build a layout with full-bleed elements while one element stays stuck to the top. This ended up being rather tricky to pull off so I’m documenting it here in case anyone needs to re-create this same effect. Part of the trickiness was dealing with logical positioning on small screens as well.

It’s tough to describe the effect, so I recorded my screen to show what I mean. Pay special attention to the main call to action section, the one with the “Try Domino Today” header.

The idea is to display the main call to action on the right side while users scroll past other sections on larger viewports. On smaller viewports, the call to action element has to display after the main hero section with the “Start your trial” header.

There are a two main challenges here:

  • Make full-bleed elements that don’t interfere with the sticky element
  • Avoid duplicating the HTML

Before we dive into a couple of possible solutions (and their limitations), let’s first set up the semantic HTML structure.

The HTML

When building these kinds of layouts, one might be tempted to build duplicate call-to-action sections: one for the desktop version and the other for the mobile version and then toggle the visibility of them when appropriate. This avoids having to find the perfect place in the HTML and needing to apply CSS that handles both layout needs. I must admit, I am guilty of doing that from time to time. But this time, I wanted to avoid duplicating my HTML.

The other thing to consider is that we’re using the sticky positioning on the .box--sticky element, which means it needs to be the sibling of other elements, including full-bleed ones, for it to properly work.

Here’s the markup:

<div class="grid">    <div class="box box--hero">Hero Box</div>    <div class="box box--sticky">Sticky Box</div>    <div class="box box--bleed">Full-bleed Box</div>   <div class="box box--bleed">Full-bleed Box</div>   <!-- a bunch more of these -->  </div>

Let’s get sticky

Making sticky elements in a CSS grid layout is pretty straightforward. We add position: sticky to the .box--sticky element with a top: 0 offset, indicating where it starts to stick. Oh, and notice that we’re only making the element sticky on viewports larger that 768px.

@media screen and (min-width: 768px) {   .box--sticky {     position: sticky;     top: 0;   } }

Beware that there is a known issue with sticky positioning in Safari when it’s used with overflow: auto. It is documented over at caniuse in the known issues section:

A parent with overflow set to auto will prevent position: sticky from working in Safari.

Nice, that was easy. Let’s solve the challenge of full-bleed elements next.

Solution 1: Pseudo-elements

The first solution is something I use often: absolutely positioned pseudo-elements that stretch from one side to side. The trick here is to use a negative offset.

If we are talking about centered content, then the calculation is quite straightforward:

.full-bleed-element {   max-width: 600px;   margin-right: auto;   margin-left: auto;   padding: 20px;   position: relative;  }  .full-bleed-element::before {   content: "";   background-color: dodgerblue;    position: absolute;   top: 0;   bottom: 0;   right: calc((100vw - 100%) / -2);   left: calc((100vw - 100%) / -2); }

In short, the negative offset is the width of the viewport, 100vw, minus the width of the element, 100%, and then divided by -2, because we need two negative offsets.

Beware that there is a known bug when using 100vw, that is also documented over at caniuse:

Currently all browsers but Firefox incorrectly consider 100vw to be the entire page width, including vertical scroll bar, which can cause a horizontal scroll bar when overflow: auto is set.

Now let’s make full-bleed elements when the content is not centered. If you watch the video again, notice that there is no content below the sticky element. We don’t want our sticky element to overlap the content and that is the reason why be don’t have centered content in this particular layout.

First, we are going to create the grid:

.grid {   display: grid;   grid-gap: var(--gap);   grid-template-columns: var(--cols);   max-width: var(--max-width);   margin-left: auto;   margin-right: auto; }

We’re using custom properties which allows us to redefine the maximum width, the gap, and grid columns without redeclaring the properties. In other words, instead of redeclaring the grid-gap, grid-template-columns, and max-width properties, we are re-declaring variable values:

:root {   --gap: 20px;   --cols: 1fr;   --max-width: calc(100% - 2 * var(--gap)); }  @media screen and (min-width: 768px) {   :root {     --max-width: 600px;     --aside-width: 200px;     --cols: 1fr var(--aside-width);   } }  @media screen and (min-width: 980px) {   :root {     --max-width: 900px;     --aside-width: 300px;   } }

On viewports that are 768px wide and above, we have defined two columns: one with a fixed width, --aside-width, and one with that fills the remaining space, 1fr, as well as maximum width of the grid container, --max-width.

On viewports smaller than 768px, we have defined a single column and the gap. The maximum width of the grid container is 100% of the viewport, minus gaps on each side.

Now comes the fun part. The content isn’t centered on bigger viewports, so the calculation isn’t as straightforward as you might think. Here’s how it looks:

.box--alpha {   position: relative;   z-index: 0; }  .box--alpha::before {   content: "";   display: block;   position: absolute;   top: 0;   bottom: 0;   left: calc((100vw - (100% + var(--gap) + var(--aside-width))) / -2);   right: calc(((100vw - (100% - var(--gap) + var(--aside-width))) / -2) - (var(--aside-width)));   z-index: -1; }

Instead of using 100% of the parent’s width, we’re taking into account the widths of the gap and the sticky element. That means width of the content in full-bleed elements will not exceed the bounds of the hero element. That way, we ensure the sticky element won’t overlap any important piece of information.

The left offset is simpler because we only need to subtract the width of the element (100%), the gap (--gap), and the sticky element (--aside-width) from the viewport width (100vw).

left: (100vw - (100% + var(--gap) + var(--aside-width))) / -2);

The right offset is more complicated because we have to add the width of the sticky element to the previous calculation, --aside-width, as well as the gap, --gap:

right: ((100vw - (100% + var(--gap) + var(--aside-width))) / -2) - (var(--aside-width) + var(--gap));

Now we are sure the sticky element doesn’t overlap any content in full-bleed elements.

Here’s the solution with a horizontal bug:

And here’s the solution with a horizontal bugfix:

The fix is to hide overflow on the x-axis of the body, which might be a good idea in general anyway:

body {   max-width: 100%;   overflow-x: hidden; }

This is a perfectly viable solution and we could end here. But where’s the fun in that? There’s usually more than one way to accomplish something, so let’s look at another approach.

Solution 2: Padding calculations

Instead of using a centered grid container and pseudo elements, we could achieve the same effect by configuring our grid. Let’s start by defining the grid just as we did last time:

.grid {   display: grid;   grid-gap: var(--gap);   grid-template-columns: var(--cols); }

Again, we are using custom properties to define the gap and the template columns:

:root {   --gap: 20px;   --gutter: 1px;   --cols: var(--gutter) 1fr var(--gutter); }

We’re showing three columns on viewports smaller than 768px. The center column takes as much space as possible, while the other two are used only to force the horizontal gap.

@media screen and (max-width: 767px) {   .box {     grid-column: 2 / -2;   } }

Note that all grid elements are placed in the center column.

On viewports bigger than 768px, we are defining a --max-width variable that limits the width of the inner columns. We’re also defining --aside-width, the width of our sticky element. Again, this way we ensure the sticky element won’t be positioned over any content inside the full-bleed elements.

:root {   --gap: 20px; }  @media screen and (min-width: 768px) {   :root {     --max-width: 600px;     --aside-width: 200px;     --gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));     --cols: var(--gutter) 1fr var(--aside-width) var(--gutter);   } }  @media screen and (min-width: 980px) {   :root {     --max-width: 900px;     --aside-width: 300px;   } }

Next, we are calculating the gutter width. The calculation is:

--gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));

…where 100% is the viewport width. First, we are subtracting the maximum width of the inner columns from the width of the viewport. Then, we are dividing that result by 2 to create the gutters. Finally, we are subtracting the grid’s gap to get the correct width of the gutter columns.

Now let’s push the .box--hero element over so it starts at the first inner column of the grid:

@media screen and (min-width: 768px) {   .box--hero {     grid-column-start: 2;   } }

This automatically pushes the sticky box so it starts right after the hero element. We could also explicitly define the placement of the sticky box, like this:

.box--sticky {   grid-column: 3 / span 1; }

Finally, let’s make the full-bleed elements by setting grid-column to 1 / -1. That tells the elements to start the content at the first grid item and span through to the last one.

@media screen and (min-width: 768px) {     .box--alpha {     grid-column: 1 / -1;   } }

To center the content, we are going to calculate left and right padding. The left padding is equal to the size of the gutter column, plus the grid gap. The right padding is equal to the size of the left padding, plus another grid gap as well as the width of the sticky element.

@media screen and (min-width: 768px) {   .box--alpha {       padding-left: calc(var(--gutter) + var(--gap));     padding-right: calc(var(--gutter) + var(--gap) + var(--gap) + var(--aside-width));   } }

Here’s the final solution:

I prefer this solution to the first one because it isn’t using buggy viewport units.


I love CSS calculations. Using mathematical operations is not always straightforward, especially when combining different units, like 100%. Figuring out what 100% means is half of the effort.

I also love solving simple, yet complicated layouts, like this one, using only CSS. Modern CSS has native solutions — like grid, sticky positioning and calculations — that remove complicated and somewhat heavy JavaScript solutions. Let’s leave the dirty work for the browser!

Do you have a better solution or different approach for this? I would be happy to hear about it.


The post How to Get Sticky and Full-Bleed Elements to Play Well Together appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

A Dynamically-Sized Sticky Sidebar with HTML and CSS

Creating page content that sticks to the viewport as you scroll, something like a jump-to-anchor menu or section headings, has never been easier. Throw a position: sticky into your CSS ruleset, set the directional offset (e.g. top: 0) and you’re ready to impress your teammates with minimal effort. Check out this CSS-Tricks article to see some real fancy sticky positioning use cases.

But sticky positioning can get a bit tricky, particularly when it comes to height and the dangerous situation of hiding content in a position that can’t be scrolled to. Let me set the stage and show you the problem and how I fixed it.

I recently worked on a desktop layout that we’re all familiar with: a main content area with a sidebar next to it. This particular sidebar contains action items and filters that are pertinent to the main content. As the page section is scrolled, this component remains fixed to the viewport and contextually accessible.

The layout styling was as easy to implement as I had mentioned earlier. But there was a catch: The height of the component would vary based on its content. I could have capped it with a max-height and set overflow-y: auto to make the component content scrollable. This worked well on my laptop screen and my typical viewport height, but in a smaller viewport with less vertical real estate, the sidebar’s height would exceed the viewport.

When the sticky sidebar height is larger than the viewport, some of its content becomes inaccessible until reaching the bottom of the container, when the element is no longer sticky.

That’s where things got tricky.

Thinking through solutions

I initially considered reaching for a media query. Perhaps I could use a media query to remove the sticky positioning and have the component sit relative to the top of the sidebar container. This would grant access to the entirety of its content. Otherwise, when scrolling the page, the sticky component’s content is cut off at the bottom of the viewport until I reach the end of its parent section.

Then I remembered that the height of the sticky component is dynamic.

What magic value could I use for my media query that would handle such a thing? Perhaps instead I could write a JavaScript function to check if the component flows beyond the viewport boundaries on page load? Then I could update the component’s height…

That was a possibility.

But what if the user resizes their window? Should I use that same function in a resize event handler? That doesn’t feel right. There must be a better way to build this.

Turns out there was and it involved some CSS trickery to get the job done!

Setting up the page section

I started with a flex display on the main element. A flex-basis value was set to the sidebar for a fixed desktop width. Then the article element filled the rest of the available horizontal viewport space.

If you’re curious about how I got the two containers to stack for smaller viewports without a media query, check out The Flexbox Holy Albatross trick.

I added align-self: start to the sidebar so its height wouldn’t stretch with the main article (stretch  is the default value). This gave my positioning properties the ability to cast their magic:

.sidebar {   --offset: var(--space);   /* ... */   position: sticky;   top: var(--offset); }

Check that out! With these two CSS properties, the sidebar element sticks to the top of the viewport with an offset to give it some breathing room. Notice that the top value is set to a scoped CSS custom property. The --offset variable can now be reused on any child element inside the sidebar. This will come in handy later when setting the sticky sidebar component’s maximum height.

You can find a list of global CSS variable declarations in the CodePen demo, including the --space variable used for the offset value in the :root ruleset.

The sticky sidebar

Keep in mind that the component itself is not what is sticky; it’s the sidebar itself. General layout and positioning should typically be handled by the parent. This gives the component more flexibility and makes it more modular to use in other areas of the application.

Let’s dive into the anatomy of this component. In the demo, I’ve removed the decorative properties below to focus on the layout styles:

.component {   display: grid;   grid-template-rows: auto 1fr auto; } 
 .component .content {   max-height: 500px;   overflow-y: auto; }
  • This component uses CSS Grid and the pancake stack idea from 1-Line Layouts to configure the rows of this template. Both the header and footer (auto) adjust to the height of their children while the content (1fr, or one fraction unit) fills up the rest of the open vertical space.
  • A  max-height on the content limits the component’s growth on larger screen sizes. This is unnecessary if it’s preferred that the component stretch to fill the viewport height.
  • overflow-y: auto allows the content to be scrolled when necessary.

When the component is being used in the sidebar, a max-height is needed so that it doesn’t exceed the viewport height. The --offset previously scoped to the .sidebar class is doubled to create a margin on the bottom of the element that matches the top offset of the sticky sidebar:

.sidebar .component {   max-height: calc(100vh - var(--offset) * 2); }

That wraps up the assembly of this sticky sidebar component! After some decorative styles were applied, this prototype became ready for testing and review. Give it a try! Open up the demo in CodePen and click on the grid items to add them to the sidebar. Resize your browser window to see how the component flexes with the viewport while staying in view as you scroll the main content section.


This layout may work well on a desktop browser, but isn’t entirely ideal for smaller devices or viewport widths. However, the code here provides a solid foundation that makes it easy to add improvements to the UI.

One simple idea: A button could be affixed to the viewport window that, when clicked, jumps the page down to the sidebar content. Another idea: The sidebar could be hidden off-screen and a toggle button could slide it in from the left or right. Iteration and user testing will help drive this experience in the right direction.


The post A Dynamically-Sized Sticky Sidebar with HTML and CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

How to Detect When a Sticky Element Gets Pinned

Totally agree with David, on CSS needing a selector to know if a position: sticky; element is doing its sticky thing or not.

Ideally there would be a :stuck CSS directive we could use, but instead the best we can do is applying a CSS class when the element becomes sticky using a CSS trick and some JavaScript magic

I love it when there is a solution that isn’t some massive polyfill or something. In this case, a few lines of IntersectionObserver JavaScript and tricky usage of top: -1px in the CSS.

Direct Link to ArticlePermalink


The post How to Detect When a Sticky Element Gets Pinned appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

How to Use CSS Grid for Sticky Headers and Footers

CSS Grid is a collection of properties designed to make layout easier than it’s ever been. Like anything, there’s a bit of a learning curve, but Grid is honestly fun to work with once you get the hang of it. One area where it shines is dealing with headers and footers. With a little adjustment in our thinking, we can pull off headers and footers that behave like they are fixed, or have that “sticky” treatment (not position: sticky, but the kind of footer that hugs the bottom of the screen even if there isn’t enough content to push it there, and is pushed away with more content). 

Hopefully this sparks further interest in modern layouts, and if it does, I can’t recommend Rachel Andrew’s book The New CSS Layout strongly enough: it covers both of the major modern layout techniques, grid and flexbox.

What we’re making

Let’s implement a fairly classic HTML layout that consist of a header, main content and footer.

We’ll make a truly fixed footer, one that stays at the bottom of the viewport where the main content scrolls within itself, as needed, then later update the footer to be a more traditional sticky footer that starts at the bottom of the viewport, even if the main content is small, but gets pushed down as needed. Further, to broaden our exposure to grid, let’s design our main content holder so that it can either span the whole width of the viewport, or take up a nicely centered strip down the middle.

A fixed footer is slightly unusual. Footers are commonly designed to start at the bottom of the viewport, and get pushed down by main content as needed. But a persistent footer isn’t unheard of. Charles Schwab does it on their homepage. Either way, it’ll be fun to implement!

But before we move on, feel free to actually peek at the fixed footer implemented on the Charles Schwab site. Unsurprisingly, it uses fixed positioning, which means it has a hard-coded size. In fact, if we crack open DevTools, we see that right off the bat:

body #qq0 {   border-top: 4px solid #133568;   background-color: #eee;   left: 0;   right: 0;   bottom: 0;   height: 40px!important; }

Not only that, but there’s the balance of making sure the main content doesn’t get hidden behind that fixed footer, which it does by setting hard-coded paddings (including 15px on the bottom of the <footer> element), margins (including 20px on <ul> in the footer), and even line breaks.

Let’s try to pull this off without any of these restrictions.  

Our baseline styles

Let’s sketch out a bare minimum UI to get us started, then enhance our grid to match our goals. There’s a CodeSandbox below, plus additional ones for the subsequent steps that get us to the end result.

First, let’s do some prep work. We’ll make sure we’re using the whole height of the viewport, so when we add our grid, it’ll be easy to put the footer at the bottom (and keep it there).  There’s only going to be one element inside the document’s <body> with an ID of #app, which will hold the <header, <main> and <footer> elements.

body {   margin: 0; /* prevents scrollbars */ } 
 #app {   height: 100vh; }

Next, let’s set up our header, main, and footer sections, as well as the grid they’ll all sit in. To be clear, this will not work the way we want right out of the gate. It’s just to get us started, with a base to build from.

body {   margin: 0; } 
 #app {   height: 100vh;      /* grid container settings */   display: grid;   grid-template-columns: 1fr;   grid-template-rows: auto 1fr auto;   grid-template-areas:      'header'     'main'     'footer'; } 
 #app > header {   grid-area: header; } 
 #app > main {   grid-area: main;   padding: 15px 5px 10px 5px; } 
 #app > footer {   grid-area: footer; }

We’ve created a simple one-column layout, with a width of 1fr. If that 1fr is new to you, it essentially means “take the remaining space” which, in this case, is the entire width of the grid container, #app.

We’ve also defined three rows:

#app {   /* etc. */   grid-template-rows: auto 1fr auto;   /* etc. */ }

The first and third rows, which will be our header and footer, respectively, are sized with auto, which means they’ll take up as much space as needed. In other words: no need for hard-coded sizes! This is a super important detail and a perfect example of how we benefit from using CSS Grid.

The middle row is where we’ll put our content. We’ve assigned it a size of 1fr which, again, just means it takes up all of the remaining space that’s left over from the other two rows. If you’re wondering why we aren’t making it auto as well, it’s because the entire grid spans the viewport’s whole height, so we need one section to grow and fill up any unused space. Note that we do not have, nor will we ever need at any point, any fixed heights, margins, paddings — or even line breaks! — to push things into place. Such is the good life when working with grid!

Shall we try some content?

You’ll notice in the Sandbox that I used React to build this demo, but since this isn’t a post about React, I won’t belabor those details; React has absolutely nothing to do with any of the CSS Grid work in this post. I’m only using it as an easy way to navigate between different chunks of markup. If you hate React, that’s fine: hopefully you can ignore it in this post.

We have Header, Main and Footer components that render the expected <header> , <main>  and <footer> elements, respectively. And, of course, this all sits inside our #app container. Yes, in theory, #app should be an <article> element, semantically speaking, but that’s always looked weird to me. I just wanted to covey these details so we’re all one the same page as we plow ahead.

For the actual content, I have Billing and Settings sections that you can navigate between in the header. They both render fake, static content, and are only meant to show our layout in action. The Settings section will be the content that we put in a centered strip on our page, Billing will be the one that spans our whole page.

Here’s the Sandbox with what we have so far.

The Billing section looks good, but the Settings section pushes our footer off screen. Not only that, but if we scroll, the entire page scrolls, causing us to lose our header. That may be desirable in some cases, but we want both the header and footer to stay in view, so let’s fix that.

Fixed header, fixed footer

When we initially set up our grid, we gave it a height of 100vh, which is the entire height of the viewport. We then assigned the rows for the header and footer an auto height, and the main a height of 1fr to take up the remaining space. Unfortunately, when content exceeds the space available, it expanded beyond the viewport bounds, pushing our footer down and out of view.

The fix here is trivial: adding overflow: auto will cause our <main> element to scroll, while keeping our <header> and <footer> elements in place.

#app > main {   grid-area: main;   overflow: auto;   padding: 15px 5px 10px 5px; }

Here’s the updated demo that puts this to use.

Adjustable width main section

We want our <main> element to either span the whole width of the viewport, or be centered in a 600px space. You might think we could simply make <main> a 600px fixed width, with an auto margins on either side. But since this is a post about grid, let’s use moar grid. (Plus, as we’ll see later, a fixed width won’t work anyway).

To achieve our centered 600px element, we’ll actually make the <main> element a grid container. That’s right, a grid within a grid! Nesting grids is a totally legit approach, and will even get easier in the future when subgrid is officially supported across browsers. In this scenario, we’ll make <main> a grid with three column tracks of 1fr 600px 1fr or, stated simply, 600px in the middle, with the remaining space equally divided on the sides.

#app > main {   display: grid;   grid-template-rows: 1fr;   grid-template-columns: 1fr 600px 1fr; }

Now let’s position our the content in the grid. Our different modules all render in a <section> child. Let’s say that by default, content will occupy the middle section, unless it has a .full class, in which case it will span the entire grid width. We won’t use named areas here, and instead specify precise grid coordinates of the form [row-start] / [col-start] / [row-end] / [col-end]:

#app > section {   grid-area: 1 / 2 / 1 / 3; } 
 #app > section.full {   grid-area: 1 / 1 / 1 / 4 }

You might be surprised to see a col-end value of 4, given that there’s only three columns. This is because the column and row values are column and row grid lines. It takes four grid lines to draw three grid columns. 

Our <section> will always be in the first row, which is the only row. By default it’ll span column lines 2 through 3, which is the middle column, unless the section has a full class on it, in which case it’ll span column lines 1 through 4, which is all three columns.

Here’s an updated demo with this code. It’ll probably look good, depending on your CodeSandbox layout, but there’s still a problem. If you shrink the display to smaller than 600px, the content is abruptly truncated. We don’t really want a fixed 600px width in the middle. We want a width of up to 600px. It turns out grid has just the tool for us: the minmax() function. We specify a minimum width and a maximum width, and the grid will compute a value that falls in that range. That’s how we prevent the content from blowing out of the grid.

All we need to do is swap out that 600px value with minmax(0, 600px):

main {   display: grid;   grid-template-rows: 1fr;   grid-template-columns: 1fr minmax(0, 600px) 1fr; }

Here’s the demo for the finished code.

One more approach: The traditional fixed footer

Earlier, we decided to prevent the footer from being pushed off the screen and did that by setting the <main> element’s overflow property to auto.

But, as we briefly called out, that might be a desirable effect. In fact, it’s more of a classic “sticky” footer that solves that annoying issue, and places the footer on the bottom edge of the viewport when the content is super short.

Hey, get back to the bottom!

How could we keep all of our existing work, but allow the footer to get pushed down, instead of fixing itself to the bottom in persistent view?

Right now our content is in a grid with this HTML structure:

<div id="app">   <header />   <main>     <section />   </main>   <footer /> </div>

…where <main> is a grid container nested within the #app grid container, that contains one row and three columns that we use to position our module’s contents, which go in the <section> tag.

 Let’s change it to this:

<div id="app">   <header />   <main>     <section />     <footer />   </main> </div>

…and incorporate <footer> into the <main> element’s grid. We’ll start by updating our parent #app grid so that it now consists of two rows instead of three:

#app {   /* same as before */ 
   grid-template-columns: 1fr;   grid-template-rows: auto 1fr;   grid-template-areas:      'header'     'main'; }

Just two rows, one for the header, and the other for everything else. Now let’s update the grid inside our <main> element:

#app > main {   display: grid;   grid-template-rows: 1fr auto;   grid-template-columns: 1fr minmax(0, 600px) 1fr; }

We’ve introduced a new auto-sized row. That means we now have a 1fr row for our content, that holds our <section>, and an auto row for the footer.

Now we position our <footer> inside this grid, instead of directly in #app:

#app > footer {   grid-area: 2 / 1 / 2 / 4; }

Since <main> is the element that has scrolling, and since this element now has our footer, we’ve achieved the sticky footer we want! This way, if <main> has content that exceeds the viewport, the whole thing will scroll, and that scrolling content will now include our footer, which sits at the very bottom of the screen as we’d expect.

Here’s an updated demo. Note that the footer will be at the bottom of the screen if possible; otherwise it’ll scroll as needed. 

I made a few other small changes, like minor adjustments to paddings here and there; we can’t have any left or right paddings on <main>, because the <footer> would no longer go edge-to-edge.

I also made a last-minute adjustment during final edits to the <section> element—the one we enabled adjustable width content on. Specifically, I set its display to flex, its width to 100%, and its immediate descendant to overflow: auto. I did this so the <section> element’s content can scroll horizontally, within itself, if it exceeds our grid column boundary, but without allowing any vertical scrolling.

Without this change, the work we did would amount to the fixed footer approach we covered earlier. Making section> a flex container forces its immediate child — the <div> that contains the content — to take up all of the available vertical space. And, of course, setting that child div to overflow: auto enables scrolling. If you’re wondering why I didn’t just set the section’s overflow-x to auto, and overflow-y to visible, well, it turns out that’s not possible.

Parting thoughts 

We haven’t done anything revolutionary in this post, and certainly nothing that couldn’t be accomplished before CSS Grid. Our fixed width <main> container could have been a block element with a max-width value of 600px, and auto margins on the left and right. Our fixed footer could have been made with position: fixed (just make sure the main content doesn’t overlap with it). And, of course, there are various ways to get a more traditional “sticky footer.”

But CSS Grid provides a single, uniform layout mechanism to accomplish all of this, and it’s fun to work with — honestly fun. In fact, the idea of moving the footer from fixed to sticky wasn’t even something I planned at first. I threw it in at the last minute because I thought the post was a bit too light without it. It was trivial to accomplish, basically moving grid rows around, not unlike putting lego blocks together. And again, these UIs were trivial. Imagine how brightly grid will shine with more ambitious designs!


The post How to Use CSS Grid for Sticky Headers and Footers appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Stacked Cards with Sticky Positioning and a Dash of Sass

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

I started wondering how much JavaScript this would involve and how you’d go about making it when I realized — ah! — this must be the work of position: sticky and a tiny amount of Sass. So, without diving into how Corey did this, I decided to take a crack at it myself.

First up, some default styles for the cards:

body {   background: linear-gradient(#e8e8e8, #e0e0e0); }  .wrapper {   margin: 0 auto;   max-width: 700px; }  .card {   background-color: #fff;   border: 1px solid #ccc;   border-radius: 10px;   box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1);   color: #333;   padding: 40px; }

Next, we need to make each card sticky to the top of the wrapper. We can do that like this:

.card {   position: sticky;   top: 10px;   // other card styles }

And that leaves us with this:

But how do we get each of these elements to look like a stack on top of one another? Well, we can use some fancy Sass magic to fix the position of each card. First we’ll loop over every card element and then change the value with each iteration:

@for $ i from 1 through 8 {   .card:nth-child(#{$ i}n) {     top: $ i * 20px;   } }

Which results in this demo, which is totally charming, if I do say so myself:

And there we have it! We could make a few visual changes here to improve things. For example, the box-shadow and color of each card, just like Corey’s example. But I wanted to keep experimenting here. What if we switch the order of the cards and made them horizontal instead?

We already do that on this very website:

After experimenting for a little bit I changed the order of the cards with flexbox and made each item slide in from right to left:

.wrapper {   display: flex;   overflow-x: scroll; }  .card {   height: 60vh;   min-width: 50vw;   position: sticky;   top: 5vh;   left: 10vw; }

But I also wanted to make each of the cards come in at different angles so I updated the Sass loop with the random function:

@for $ i from 1 through 8 {   .card:nth-child(#{$ i}n) {     left: $ i * 20px;     left: random(200) + $ i * 1px;     top: random(130) + $ i * 1px;     transform: rotate(random(3) - 2 * 1deg);   } }

That’s the bulk of the changes and that results in the following:

Pretty neat, eh? I love position: sticky; so much.


The post Stacked Cards with Sticky Positioning and a Dash of Sass appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Solving Sticky Hover States with @media (hover: hover)

Mezo Istvan does a good job of covering the problem and a solution to it in a blog post on Medium¹.

If you tap on something that has a :hover state but you don’t leave the page then, on a mobile device, there is a chance that :hover state “sticks.” You’ll see this with stuff like jump-links used as tabs or buttons that trigger on-page functionality.

button:hover {   border: 3px solid green; /* might stick! */ }

The solution, or trick, is a new(ish) “CSS4” media query that allows you only to apply styles on devices with hover capability.

@media (hover: hover) {   button:hover {     border: 3px solid green; /* solves sticky problem */   } }

Your typical touch screen mobile device will fail that media query, the style won’t apply, and you’ll avoid the sticky problem.

Support is solid, so not much worry there.

  1. It almost feels like we have to apologize to linking to things on Medium lately. I have no idea what you’re going to experience when you get there. Will you just be able to read it? Will it be a teaser where you have to log in to read more? Will it be behind a paywall? I have no idea. In this case, hopefully, this link post has enough info in it that isn’t not blocking you from learning anything.

Direct Link to ArticlePermalink

The post Solving Sticky Hover States with @media (hover: hover) appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Getting Fancy with position: sticky;

Mike Solomon worked on a fancy scrollytelling post for Esquire and blogged about it. It has GIFs of each step along the way of figuring out not just position: sticky; but also using negative margins, wrapper divs, backgrounds, and even a smidge of JavaScript measuring to get it all right.

What it doesn’t have is any isolated demo of the effect. I figured I’d give a crack at reverse engineering it.

Here’s mine, which I’ll call “Sticky Figcaption with Protruding Figure”:

That demo is full of magic numbers to make the exit do the “tuck behind” effect. If that’s not important, this version is much cleaner.

Probably not quiteas Mike had it, but I’m not privy to the exact details he was going for in the blog post. His final GIF is:

Here’s a quick video I’ll shoot from the article itself in case that inspires you to figure out a different approach:


Erp! I actually spoke with Mike about all this, and he says that the main takeaway from all this (which flew right over my head — sorry Mike!) is that “sticky isn’t just for the top of the screen.” Notice in the final product how the sticky element becomes sticky long before it becomes the element at the top of the screen. It’s more like the middle of the screen. That’s what the top value is for with position: sticky; but, in this demo where the goal is to have it slide in and out of an image, it gets tricky.

After some back and forth forking…

Direct Link to ArticlePermalink

The post Getting Fancy with position: sticky; appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Sticky Table of Contents with Scrolling Active States

Say you have a two-column layout: a main column with content. Say it has a lot of content, with sections that requires scrolling. And let’s toss in a sidebar column that is largely empty, such that you can safely put a position: sticky; table of contents over there for all that content in the main column. A fairly common pattern for documentation.

Bramus Van Damme has a nice tutorial on all this, starting from semantic markup, implementing most of the functionality with HTML and CSS, and then doing the last bit of active nav enhancement with JavaScript.

For example, if you don’t click yourself down to a section (where you might be able to get away with :target styling for active navigation), JavaScript is necessary to tell where you are scrolled to an highlight the active navigation. That active bit is handled nicely with IntersectionObserver, which is, like, the perfect API for this.

Here’s that result:

It reminds me of a very similar demo from Hakim El Hattab he called Progress Nav. The design pattern is exactly the same, but Hakim’s version has this ultra fancy SVG path that draws itself along the way, indenting for sub nav. I’ll embed a video here:

That one doesn’t use IntersectionObserver, so if you want to hack on this, combine ’em!

The post Sticky Table of Contents with Scrolling Active States appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Position Sticky and Table Headers

You can’t position: sticky; a <thead>. Nor a <tr>. But you can sticky a <th>, which means you can make sticky headers inside a regular ol’ <table>. This is tricky stuff, because if you didn’t know this weird quirk, it would be hard to blame you. It makes way more sense to sticky a parent element like the table header rather than each individiaul element in a row.

The issue boils down to the fact that stickiness requires position: relative to work and that doesn’t apply to <thead> and <tr> in the CSS 2.1 spec.

There are two very extreme reactions to this, should you need to implement sticky table headers and not be aware of the <th> workaround.

  • Don’t use table markup at all. Instead, use different elements (<div>s and whatnot) and other CSS layout methods to replicate the style of a table, but not locked out of using position: relative and creating position: sticky parent elements.
  • Use table elements, but totally remove all their styling defaults with new display values.

The first is dangerous because you aren’t using semantic and accessible elements for the content to be read and navigated. The second is almost the same. You can go that route, but need to be really careful to re-apply semantic roles.

Anyway, none of that matters if you just stick (get it?!) to using a sticky value on those <th> elements.

See the Pen
Sticky Table Headers with CSS
by Chris Coyier (@chriscoyier)
on CodePen.

It’s probably a bit weird to have table headers as a row in the middle of a table, but it’s just illustrating the idea. I was imagining colored header bars separating players on different sports teams or something.

Anytime I think about data tables, I also think about how tricky it can be to make them responsive. Fortunately, there are a variety of ways, all depending on the best way to group and explore the data in them.

The post Position Sticky and Table Headers appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]