Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new ring utilities for custom focus styles and rounded outlines #2747

Merged
merged 4 commits into from Nov 9, 2020

Conversation

adamwathan
Copy link
Member

This PR adds a set of new ring utilities, designed to make it easier to give elements custom focus styles.

Here's a quick overview of what they look like in practice:

<div class="bg-gray-900">
  <button class="bg-white ... focus:ring-2 ring-offset-2 ring-offset-gray-900 ring-white ring-opacity-50">
    Click me
  </button>
</div>

This will render a custom focus ring (implemented with a box shadow to respect corner radius) with the following visual properties:

  • 2px thick
  • Offset by 2px so it's not hugging the button directly
  • The offset area is gray-900 to match the background of the panel behind the button
  • The ring itself is white, with 50% opacity

It looks like this:

image

These utilities live inside the following new core plugins:

  • ringWidth, for ring-2
  • ringOffsetWidth, for ring-offset-2
  • ringOffsetColor, for ring-offset-gray-900
  • ringColor, for ring-white
  • ringOpacity, for ring-opacity-50

The ringWidth plugin has focus variants enabled by default, but the rest only have responsive enabled to reduce file size. You only need to add the focus: prefix to the ringWidth utilities anyways.

Take a look at the changes to the default config file to get a full picture of what has been added/enabled by default.


Design possibilities

This API is extremely composable and flexible and makes it very easy to create an endless number of custom focus styles without making any changes to your configuration file.

Here are some examples of different styles that are all possible with this API out of the box:

image

It can also be used to create border effects in situations where you want to add a border that doesn't impact the layout, for example in an avatar group like this:

image


Detailed implementation notes

Compatible with existing shadows

This is implemented in a way that is fully composable with the existing shadow utilities. Basically this is the CSS we generate (pseudofied to make it easier to understand):

.shadow-sm {
  --box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  box-shadow: var(--box-shadow);
}

/* ... */

.ring-2 {
  --ring-width: 2px;
  box-shadow: var(--ring-offset-shadow), var(--ring-shadow), var(--box-shadow);
}

This ensures that if an element has a regular shadow like shadow-lg applied to it, it's safe to apply a ring utility on top of it without overriding the original shadow.

How offsets work

The actual ring shadow width is calculated dynamically by taking the desired width, and adding the width of the desired offset, like this:

.ring-2 {
  --ring-width: 2px;
  --ring-width-combined: calc(var(--ring-width) + var(--ring-offset-width, 0px));
  /* ... */
}
.ring-offset-4 {
  --ring-offset-width: 4px;
}

How ring opacity works

Ring opacity is implemented the same way border opacity/background opacity/etc. is, using CSS variables:

.ring-black {
  --ring-opacity: 1;
  --ring-color: rgba(0, 0, 0, var(--ring-opacity));
}

/* ... */

.ring-opacity-50 {
  --ring-opacity: 0.5;
}

Default values

If you simply apply the ring or focus:ring classes, you'll get a 3px ring using a semi-transparent blue color, very similar to the shadow-outline class. This approach replaces the shadow-outline class.


Breaking changes

This is a very powerful feature and replaces the need for shadow-outline, shadow-xs, and shadow-solid, so those have been removed in this PR.

Migrating from shadow-outline to ring is as trivial as doing a global find and replace since focus:ring does the same thing as focus:shadow-outline did.

I've added opacity-5 and opacity-95 so that shadow-xs can be easily recreated using ring-1 ring-black ring-opacity-5.

shadow-solid was never in a proper Tailwind release (although it is used in Tailwind UI and provided by the @tailwindcss/ui plugin) so that's not a huge deal, but it can also be recreated using ring-2 ring-current

@adamwathan adamwathan merged commit d4fcd2e into master Nov 9, 2020
@adamwathan adamwathan deleted the ring-utilities branch November 9, 2020 21:39
@florianbouvot
Copy link
Contributor

@adamwathan I wonder why shadow-xs was move to ring utilities? 🤔

@surjithctly
Copy link

surjithctly commented Nov 10, 2020

Down Vote 👎 to remove shadow-xs in favor of ring, Doesn't make sense to add 3 classes to create an effect which was previously achieved using just one class. (Since its also used in production on many sites)

I guess shadow-xs and ring utilities can co-exist.

A simple code search shows 261,717 results for 'shadow-xs', which means those 261k files are going to break.

@adamwathan
Copy link
Member Author

You can add it back to your config file if you want to very easily 👍🏻 we added shadow-xs for a very specific use case we had which was to add a 1px semi transparent border, and stack it with other shadow using an extra element. It's much cleaner to implement as a ring, and lets you avoid adding an extra div just to stack the extra shadow.

It's a 1px solid shadow with no offset and no blur, so it's definitely a ring.

@zaydek
Copy link

zaydek commented Nov 10, 2020

I’ve wondered about what implementing a ring API would look like.

For what it’s worth, Netflix uses some trickery with a pseudo element. This benefit of this approach is that ring-offset-{color} does not obscure the background layer. Of course, this isn’t really necessary, but I think this kind of approach has real-world benefits, e.g. less surprising user-result.

Screen Shot 2020-11-10 at 9 00 23 PM

@florianbouvot
Copy link
Contributor

You can add it back to your config file if you want to very easily 👍🏻 we added shadow-xs for a very specific use case we had which was to add a 1px semi transparent border, and stack it with other shadow using an extra element. It's much cleaner to implement as a ring, and lets you avoid adding an extra div just to stack the extra shadow.

It's a 1px solid shadow with no offset and no blur, so it's definitely a ring.

I know I can add it but I want to understand you vision. Thank you, I will update my currents projects to last alpha 😉

@florianbouvot
Copy link
Contributor

florianbouvot commented Nov 10, 2020

Migration looks harder when you are using this kind of stuff:

$component-focus-box-shadow: theme('boxShadow.outline');

EDIT: I think the simple way is to remove tailwind theme and hard code outline.

$component-focus-box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);

@adamwathan you think there is another solution?

@florianbouvot
Copy link
Contributor

florianbouvot commented Nov 11, 2020

@adamwathan I find a way after looking at your work.

If you work with sass and want to allow a components to work with ring and shadow utilities.

// Sass variable to set the fallback ring color
$component-focus-ring-color: theme('colors.blue.600') !default;

.component:focus {
  // Remove outline
  outline: none;

  // Set tailwind ring custom property (and use sass fallback)
  --tw-ring-inset: "";
  --tw-ring-offset-width: 0px;
  --tw-ring-offset-color: #ffffff;
  --tw-ring-color: #{$component-focus-ring-color};
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--ring-offset-shadow), var(--ring-shadow), var(--box-shadow, 0 0 #0000);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants