Tuesday, September 2, 2014

Triggering CSS-Only :hover and :focus Transitions

I've been making an attempt to use CSS-only for transitions including actually triggering them. The best mechanism to trigger a transition is using the :hover pseudo-select on an anchor tag. Other than :focus, its really the only one which will reset its state. I've seen hacks using :active and :target but it seem those are really stretching what makes sense based on the capabilities of CSS. To really eliminate JS, more state selectors are needed that enable attaching transitions and animations. For now, I'm exploring using :hover to create a simple carousel-like widget.

Before jumping into the final project, I quickly tested the idea:

See the Pen Simple No-JS Hover Animation by bseth99 (@bseth99) on CodePen.

This is a very simple case to play with the idea. With some actual styling, it would work really well as tooltip. You can see the .pop class defines the transition and the initial state of the element. The :hover selector is used to change the value which causes it to animate into view.

Moving on to the carousel idea, I setup three anchors which serve as hot spots to quickly transition to that slide in the widget. I also setup the carousel to continuously animate through each slide until the user hovers over the anchor which interrupts the normal animation and immediately transitions to the slide represented by the link:

See the Pen Hnoxg by bseth99 (@bseth99) on CodePen.

The difference in this setup compared to the simple case above, is instead of embedding the transitioning content into the anchor tag, the whole carousel is a sibling of each of the anchors. This relationship makes it possible to add other anchors tags to the slides as needed and makes the whole setup easier. Selecting those elements uses a selector I've rarely used (if ever). The "~" will select a sibling inside the same parent even if its not adjacent to another element. This is important since I have more than one anchor and clearly the first one is not immediately next to the main carousel element.

The next step is to define the transition and animation on the carousel. They are attached to the .slider element which is a child of the main carousel element. Its setup to scroll inside this container which hides the overflow to create the illusion of only one slide showing at a time. Each slide then becomes a child of slider with unique class names so they can be targeted as the mouse hovers over each anchor:

.scroller {
  transition: left 400ms ease-in-out;
  animation: autorun 5s;
  animation-delay: 500ms;
  animation-iteration-count: infinite;

Before forcing the transition, the animation needs to be turned off, otherwise, it will get jumpy:

.trigger:hover ~ .container > .scroller {
   animation: none;

Then, each position can be defined for the slides:

.trigger:hover.r ~ .container > .scroller {
  left: 0;

.trigger:hover.g ~ .container > .scroller {
  left: -100px;

.trigger:hover.b ~ .container > .scroller {
  left: -200px;

One additional improvement is to allow the user to click which will hold the current slide in place. Right now, as soon as you hover off the anchor, the carousel rewinds to the beginning and starts the animation over again. We can take advantage of the fact that when you click on an anchor it receives focus and remains there until clicking off the element. Only a few minor changes are required to add this feature. First, the above .trigger:hover styles need to include an additional .trigger:focus selector:

.trigger:hover.r ~ .container > .scroller,
.trigger:focus.r ~ .container > .scroller {
  left: 0;


And to enable the anchor to be focusable, the tabindex needs to be set:

<a href="#" class="trigger r" tabindex="0">

Here is the revised version using both :hover and :focus to create the interaction with the widget:

See the Pen iobaE by bseth99 (@bseth99) on CodePen.

Its fairly simple but if you don't need anything exotic you can get by quite well with some carefully laid out HTML which takes advantage of the abilities of CSS. While I called this a carousel, it certainly is not limited to that design. Any situation that calls for links that transition through some set of content can take advantage of this approach without any need to write a line of Javascript.