Modifier classes don't work well with CSS blueprint design tokens
ahacking opened this issue · 4 comments
Is your feature request related to a problem? Please describe.
I started working up a CSS blueprint for Doggo, and started on button styles. It became apparent that the modifier classes had some issues and limitations which I would like to discuss:
- The range of sizes is limited (I want at least 5 sizes in my design, plus a "full width" button)
- is-normal doesn't convey a size concept, and nor is it relative to the other sizes, its not obviously larger or smaller than 'is-medium'
- the fill type seems logically unsound. is-text, is-solid, is-outline conflates fill and border concepts, whilst is-text appears to mean 'no fill' and 'no outline/border'. Should these not be independent and separate concerns?
- The fill types are limited with no gradient fills ? opacity ?
- The shapes are limited.
- The modifiers are not name-spaced, the skeleton modifiers "is-circle", "is-rectangle", "is-square" etc could easily apply to shapes
- The use of 'is-modifier' rather than just 'modifier' is more verbose than it needs to be and looks odd when using selectors like
button:is(is-normal, is-medium)
Describe the solution you'd like
Why bother with presentation / style modifiers in Doggo at all?
Can we instead let the CSS blueprint / user of Doggo decide what size classes/modifiers can be used and applied?
What would prevent removing the following modifiers (given they relate to presentation/style concerns):
- Sizes
- Shapes
- Fills
Describe alternatives you've considered
I did consider expanding the size scale as many UI component frameworks have 5 scales for buttons with the default size being in the middle of the pack, with two sizes up and down on each side. An example of size modifiers would be extra-small, small, medium (default), large, and extra-large for button sizes, plus 'full-width' to expand to the width of the container.
But the more I looked at the modifiers, the more it struck me that these presentation options should be a CSS blueprint concern and not a concern of Doggo as it artificially limits the choices and unnecessarily constrains the available design tokens.
Additional context
In the interim I am going to continue developing my CSS blueprint as if the Doggo attributes that apply presentation modifiers don't exist. I will instead pass my own classes that align with the design tokens I am using.
It is likely that CSS blueprints will want to "bake in" their design tokens, and will probably wrap most of the Doggo components in any event for consistency, documentation and Storybook. This would seem to be another reason for not building presentation concerns into Doggo components.
Thank you for your input. I added these fixed modifiers so that I have sensible defaults to start with in my projects, but it's not meant to stay this way. Eventually, all modifiers are supposed to be made customizable, both with global defaults and per component. This is tracked in #160. Doggo would then be able to generate the components with the appropriate attributes, and also generate the stories with the configured variations. At least that's the vague idea at the moment.
the fill type seems logically unsound. is-text, is-solid, is-outline conflates fill and border concepts, whilst is-text appears to mean 'no fill' and 'no outline/border'. Should these not be independent and separate concerns?
I don't think it would be a good idea to have separate modifier classes for border and fill. I commonly see exactly these button variants: solid buttons, outline buttons, and buttons that look like plain text, but with the same sizing as the other ones. I've never worked on design system where the border itself was an independent dimension. The attribute variant
is already used for the semantic color variant. What would you call the attribute and values if the purpose stays the same?
The use of 'is-modifier' rather than just 'modifier' is more verbose than it needs to be and looks odd when using selectors like button:is(is-normal, is-medium)
It's three more characters, but it also makes is clear what is a modifier and what not when skimming through the code, even without registering the meaning of the word.
button:is(is-normal, is-medium)
I didn't even know that selector. I'm so used to SCSS nesting...
The modifiers are not name-spaced, the skeleton modifiers "is-circle", "is-rectangle", "is-square" etc could easily apply to shapes
I don't think modifier classes need to be name-spaced. Prefixing the modifier classes with the component name just makes them more verbose (is-skeleton-circle
). The crucial thing lies in the CSS organization: Always scope modifier classes to the component, e.g. in SCSS:
.skeleton {
&.is-circle {}
&.is-square{}
}
I use the word modifier class for a class that modifies a component here. A utility class on the other hand directly applies a design token to a CSS attribute, e.g. color-crimson-red
or weight-bold
.
Thanks for considering the above.
I was thinking about how one could make the modifiers like, sizes, and fills extensible without having to wrap the Doggo components, given component attr
is a macro.
I'm genuinely interested in the strategy for this as the last time I tried to create components dynamically the attr
macro cannot be used that way.
It would be nice to avoid manually wrapping all of the Doggo components if that can be achieved, but I am not certain it is possible with the way the attr macro works. Hence why I am resigned to the position that it would require wrapping components allow the CSS blueprint to specify its own values:
, default:
and doc:
options (and hence its own StoryBook). If you can crack this in a more elegant way that would be something.
In terms of CSS I am experimenting with just the out of the box Phoenix Tailwind asset pipeline:
- I have implemented the excellent accessible Radix colors as the basis for themes that obey contrast requirements. There is a lot of thought and care that has gone into the Radix color levels and palette composition.
- I have implemented theme switching for dark/light/theme-whatever with both media query and class based switching
- color palettes is pure CSS variables, and I have integrated into the tailwind config so it works nicely.
- I avoid the nonsense and limitations of the Tailwind dark: modifier.
I am using regular CSS @apply directives to apply the relevant tailwind utility classes to the selected elements, for example:
button:not(.no-transition) {
@apply transition-[transform,color,background-color,border-color,text-decoration-color,fill,stroke];
}
button.is-outline.is_primary {
@apply border-2 border-primary bg-transparent text-primary hover:bg-primary-hover hover:text-primary-hover focus-visible:outline-primary;
}
button.is-small {
@apply h-8 min-h-[2rem] rounded-lg px-3 text-sm;
}
button:disabled {
@apply pointer-events-none opacity-50;
}
button.is-pill {
@apply rounded-full;
}
button.is-circle {
@apply rounded-full p-0;
}
button.is-small.is-circle {
@apply w-8 min-w-[2rem];
}
I am trying to keep with the standard Phoenix asset pipeline (so no Sass, no additional npm packages, no CDN resources) and still provide good control over theming, and allowing the user to override the Blueprint CSS by applying tailwind as above (or any CSS they like), and also sprinkle Tailwind utility classes whenever they really need to using the class:
attribute.
I haven't experimented with customizing the modifiers yet. If I don't find a good way to do this, then yes, it would probably be best to remove them completely from the library. We'll see. For the time being, your approach of ignoring the modifier attributes is probably best.