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

What is the best way to package a common set of classes together? #267

Closed
pixelastic opened this Issue Sep 26, 2016 · 9 comments

Comments

Projects
None yet
6 participants
@pixelastic

pixelastic commented Sep 26, 2016

Hello,

I've been using tachyons on my current project and found it really helpful in prototyping and editing an existing UI. One thing I did not manage to grasp yet is the best maintainable way to share a common set of classes accross elements.

Let's say, all the buttons in my app are .link.white.dib.ba.br2.ph3.pv2.ttu.tracked.underline-hover. How should I handle the possible variations (like size changing for CTA, colors for various actions) while still being able to easily change, say, the border-radius of all buttons at once?

The various solutions I tried so far all have drawbacks and I cannot settle on one. Here is what I tried so far:

  1. Copy-pasting the class list on each element. This works, but is cumbersome. I cannot quickly prototype by changing the style of all buttons at once (which defeats one of the main advantages of the lib), as I'll have to edit them all one by one. I also take the risk of forgetting one somewhere.
  2. Using a templating language in my HTML to include a partial that contains the whole HTML element with the correct classes already applied. Something like =partial('button'). This adds a new layer of complexity (especially if I want to add other classes to that element).
  3. Adding a new .button class with old-school CSS definitions. I then lose the whole scaling system as I'm back writing width and font-size in px or rem again.
  4. Adding a new .button class just like above, but using SCSS and @extend .link to inherit from the .link class. This is the approach I like the best so far, but I've run into specificity issues induced by the order in which CSS classes are written preventing me from overwriting properties with new Tachyons CSS classes added on the element.

How do you do on your own projects?

@ixley

This comment has been minimized.

ixley commented Sep 26, 2016

@pixelastic Option 4 sounds the most appropriate to me. Are you actually running into specificity issues, or is it just loading order? If it's the latter, try loading the Tachyons styles after all of your custom extends.

@pixelastic

This comment has been minimized.

pixelastic commented Sep 26, 2016

@ixley: My specificity issues are coming from the written order of the CSS rules when using @extend. They all have the same specificity, so the last one wins. Here is an example:

%a.button.button-A
  Button of type A

%a.button.button-A.bg-red
  Button of type 1, with a red background

%a.button.button-B
  Button of type B

%a.button.button-B.bg-red
  Button of type B, with a red background
.button {
  @extend .dib;
  @extend .ma2;
  @extend .pa3;
}
.button-A {
  @extend .bg-gray;
}
.button-B {
  @extend .bg-green;
}

Here is the visual output:
image

As you can see, the only difference between the two button types are the background color. I can override type A, but not type B. Including tachyons before or after those rules does not change anything.

This is because, when using @extend, the selectors are merged, resulting in this CSS:

.bg-gray, .button-A {
  background-color: #777; }
.bg-red {
  background-color: #ff3223; }
.bg-green, .button-B {
  background-color: #41d69f; }

They all have the same specificity, but .button-B being defined last, it takes precedence over .bg-red.

@ixley

This comment has been minimized.

ixley commented Sep 26, 2016

@pixelastic ah, I see what you're saying. Since one of the principles of Tachyons seems to be to avoid needing overrides like this, I would suggest packaging only the truly reusable pieces of your components. So in this case, your button component would not include a background color, but be paired with the appropriate Tachyons bg-color class.

Extends are great for stuff that you might want to override, but obviously, less so for things you want to override with.

@johno

This comment has been minimized.

Member

johno commented Sep 26, 2016

Hey @pixelastic,

In my projects varying from prototyping to large scale production apps I've been successful with a combination of options 1 and 2 (depending on the project).

Option 1 in practice

The best packaging for groups of classes on an element is the class attribute. It's the easiest to read, and tells the story of how a given element will look and behave. Your concern about editing the classes is definitely valid, but most of my pains using numerous classes on static html can be remedied with some sed magic (this should also apply to similar search and replace functions in other editors like Atom).

Let's say that I have the following button placed throughout the page:

<a class="f6 link dim br2 ph3 pv2 mb2 dib white bg-purple" href="#0">Button Text</a>

And I want to change br2 to br3, I'll often run s/link dim br2/link dim br3/. In this example I include a couple other classes to ensure that I only search and replace the classes I'm targeting for buttons. Though, one could also use confirmation with the c option (s/foo/bar/gc).

Going to such great lengths commanding your text editor to apply classes might seem silly at first, but learning text editing is a lot easier than dealing with css at scale. Also, I've found sed much more pleasurable to work with than writing css. Not to mention this new skill is more powerful when iterating pages as a whole. Not only can I now quickly change the border radius on buttons, I can now adjust the whitespace scale on a page, change the grid, change the border color on all elements, etc. This has improved my efficiency prototyping exponentially.

This approach, also, lends itself quite nicely for only targeting particular buttons for (CTAs, inactive, etc.). One only needs to find the particular links they want to target and toss in some new classes like pa4 f3. Then you can try out a different step on the padding scale with s/pa4 f3/pa3 f3/.

Option 2 in practice

When prototyping or creating a 1-3 page static site, a templating system isn't really required. However, when things become more ambitious in nature, it becomes essential. This is where things become extremely powerful because we can modify components/partials rather than leveraging sed.

Does this introduce more complexity? Undoubtedly. Though one can also argue that Sass does, too.

Need to add conditional classes based on properties passed into the component? No problem:

import classNames from 'classnames'

export const ListItem = ({ isDone, children }) => {
  const cx = classNames('pa3 bb b--light-silver', {
    'o-80 strike mid-gray bg-near-white': isDone
  })

  return <li className={cx} children={children} />
}

Need to pass in additional classes to a base button component?

export const Button = ({ className, ...props }) => {
  const cx = `link dim br2 white bg-purple ${className}`
  return <button className={cx} {...props} />
}

// Example usage
import Button from './components/button'

<Button className='mb2 mb3-m mb4-l pa4 f2'>CTA</Button>
<Button>Normal Button</Button>

So this option ultimately boils down to the end goals and constraints of the project.

Option 3

That's ultimately the beauty of Tachyons is you can combine old school css selectors on some core components with Tachyons as you work on a project. Down the road, those old school selectors can be replaced with Tachyons classes as things stabilize.

This isn't exactly the "Tachyons approach" but when use in tandem with Tachyons will still help your stylesheet from snowballing out of control. But now there are new classes to consider and now there isn't a clear boundary for when to include new css. Also, you will now need to consider specificity since if .button is declared near the end of the stylesheet, .bg-red could lose out to a background-color declared in .button in the specificity wars.

Option 4

This approach, unfortunately, won't work with Tachyons due to specificity problems based on the order of the source css. Nor is it a recommended approach. Tachyons was developed as an alternative to existing solutions that often abused Sass in ways that ultimately erode and break down over time (this is a whole blog post in and of itself 😸 ).


Though, ultimately, it comes down to what makes you and the teams/projects you work on productive while still remaining efficient and performant.

Another consideration is the development of a styleguide, where you can create the individual components on their own before using throughout a prototype. This can be useful for honing in on design elements before you build out an entire page/site/app.

YMMV. Hope this helps : )

@pixelastic

This comment has been minimized.

pixelastic commented Sep 27, 2016

Hey @johnotander,

Thanks for the reply!

I've never given the sed option a real try. I'm a vim user, so I'm not afraid of this kind of editing approach, but I feel it only really works when I have all my classes in the same order each time. I don't think this will scale accross several files very well. But once again, I've never really given it a proper try.

For option 2, I feel it's really tied to the templating language you're using. Your React example is straightforward, but doing the same thing in my current Middleman/HAML project will require a bit more boilerplate code and might not be as pretty. I now think it's the best way, so I'll give it another try.

You mention team productivity and I feel that telling teammates that they are expected to sed and copy-paste stuff will not be as easily accepted than using a partial/template where complexity is abstracted.

@johno

This comment has been minimized.

Member

johno commented Sep 27, 2016

Not a problem : ). I puzzled over the exact same things when I was starting out incorporating Tachyons into my first few projects. This is definitely an area where our documentation is currently a bit lacking, too 😊 .

I feel it only really works when I have all my classes in the same order each time

Yeah, to be successful with this approach each element/component, like a button or grid item, needs to share the same class order for making things easiest with sed.

doing the same thing in my current Middleman/HAML project will require a bit more boilerplate code and might not be as pretty

We've got a large scale app with erb at my workplace, and it definitely has more boilerplate involved, but it's all contained within the partial. The end consumer only sees the pretty-ness of the partial API that it exposes. So one contrived example might be:

<li class="<%= "pa3 bb b--light-silver #{is_done && 'o-80 strike mid-gray bg-near-white'}" %>">
  <%= text %>
</li>
}

The above partial definitely isn't as pretty as the React example, but IMO using it still is:

<% items.each do |item| %>
  <%= render 'todos/_item', is_done: item.is_done?, text: item.text %>
<% end %>

As we create more documentation around Tachyons and the "Tachyons way" we definitely plan on creating example uses of Tachyons in different frameworks/templating systems since this largely remains undocumented right now.

You mention team productivity and I feel that telling teammates that they are expected to sed and copy-paste stuff will not be as easily accepted than using a partial/template where complexity is abstracted.

100% agreed. When a project grows to more than a handful of pages or 1-2 team members that's usually the time that I will adopt some sort of partial/component architecture, but that comes down to a judgement call depending on a project's context.

@dangayle

This comment has been minimized.

Contributor

dangayle commented Sep 27, 2016

There's an interesting solution here, for reusable components: https://marcelosomers.com/writing/rationalizing-functional-css/#reusability-of-components

Basically, add a component class name to aid in searching and replacing, but keep all the classes the way they are.

@mrmrs

This comment has been minimized.

Member

mrmrs commented Oct 31, 2016

You don't to use class names to name components. If you want to name a component I suggest using the data-* attribute as this is exactly the type of thing it is for. For example:

<article data-component-name='blog-post'></article>

As far as using @extend I would say it goes against principles of tachyons to use extend. That doesn't mean you shouldn't do it, if it works for you that's great. I don't like to be too prescriptive of how to use code as many things are contextual. But using extend greatly reduces what I view as essential parts of tachyons which is that your css doesn't need to grow when you need to build new components. If you want to use something like extend and that is the mental model that works for you, I'd say there are better setups to use than tachyons (probably something like css modules).

I don't really believe in a best way. I've found success manually editing markup or making components with html partials. Most templating languages I've worked with allow for you to create a reusable markup pattern and inject content into it.

@illycz

This comment has been minimized.

illycz commented Jan 30, 2018

@johnotander @mrmrs you mentioned html partials or html helpers. What is your recommendation if the component is structured (Card for example: https://getbootstrap.com/docs/4.0/components/card/) and I want to change some nested element (title color for example)?

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment