skip to Main Content

I have CSS like this

*:focus-visible {
  outline: 2px solid blue; /* This value is dynamically set based on configuration */
}

I now have a button with class toggle that has

.toggle {
  /* ... many other styles */
  outline: none;
}

This outline: none is to prevent the default focus outline from showing when the button is clicked. However, I’d now like the focus-visible style defined above to apply to it, since focus-visible styles are only applied in more specific cases, when the focus really should be visible, not just when the button is clicked.

I half expected something like this to work:

.toggle:focus-visible {
  outline: inherit;
}

But if I understand this correctly, inherit does not mean it’s inheriting the value from the parent’s focus-visible styles. Is there any way to do this without the .toggle styles having to duplicate the value set earlier? In other words, is there some way to have it "fall back"?

2

Answers


  1. Set the initial value of outline (which is none) when element is unfocused.

    .toggle:not(:focus),
    .toggle:not(:focus-visible) {
          outline: none;
    }
    
    Login or Signup to reply.
  2. There is no way (AFAIK) to reset a value to what was set in another, certain ruleset.

    Instead setting it to none by default for .toggle and then "resetting" it to the "active" value on a certain state, you can clear it only when it is not in that state.

    This can be done by setting outline: none with a selector like .toggle:not(:focus-visible).

    The code will explain better than I can:

    /* As before */
    *:focus-visible {
      outline: 2px solid blue;
    }
    
    /* Do not set it at all here. */
    .toggle {
      /* ... many other styles */
      /* outline: none; */
    }
    
    /* Remove the outline only when it is not :focus-visible. */
    .toggle:not(:focus-visible) {
      outline: none;
    }
    

    This has a distinct benefit over the other approaches: users with browsers that do not support :focus-visible will get their browser’s default focus ring, instead of nothing.


    Another option would be to use a CSS custom property (AKA CSS variable) to hold the initial, default value. This would of course require that you can configure the the configuration system to set this custom property instead of outline directly.
    Then you could do something like this:

    /* Set a CSS var here instead of `outline`. */
    *:focus-visible {
      --outline: 2px solid blue;
      outline: var(--outline); /* See prose below. */
    }
    
    /* Use the outline if specified, else none. */
    .toggle {
      /* ... many other styles */
      outline: var(--outline, none);
    }
    

    This defaults to using none, but will use the value of the --outline var if it is defined.

    A possible downside to the basic approach is that everything that relied on the outline being set would now have to set it from --outline. Instead, you can set the outline property as well: "naive" elements will just use it, but any that need it can be customized (such as buttons).


    A last-ditch option: use !important in the *:focus-visible rule, like so:

    *:focus-visible {
      outline: 2px solid blue !important;
    }
    

    This should be avoided if at all possible, because !important makes things complicated and harder to maintain and modify in the long run. But sometimes you gotta do what you gotta do (although it usually means there are flaws somewhere in the system).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search