Component variants


At this point in the tutorial, we have a tiny color palette and a single button component working. But a single button won't do of course, so let's add a variant of our basic button to the pattern library:

<!-- Color palette from before -->
<div class="colorExample" style="background: navy">Primary color</div>
<div class="colorExample" style="background: #EEE">Secondary color</div>
<div class="colorExample" style="background: rgba(255, 0, 0, 0.5)">
  Error color
</div>

<!-- Definition for the basic button component -->
<button class="basic-button">Example text</button>
<style>
  .basic-button {
    color: #eee;
    background: navy;
    font-weight: bold;
    padding: 0px 1em;
  }
</style>

<!-- Definition for a variant of the basic button  -->
<button class="basic-button large">Large button</button>
<style>
  .large {
    font-size: 2em;
  }
</style>

If we now configure Warhol to use the URL for the updated pattern library with the extra button

but keep our component configuration as before

[
  {
    "source": ".basic-button",
    "target": ".btn, .button",
    "name": "Basic button"
  }
]

... what do you expect to happen? Well, Warhol will attempt to take snapshots, but will fail to do so for the button component and you better get ready for some complaints about inconsistent component styles. The problem is that the source selector for the basic button (.button) also matches the enlarged button, which of course has significant style differences from the basic button. Warhol can't know which of the two different elements is supposed to be the one and only button component and has to give up.

Composing components from different classes is a very common pattern in CSS, but Warhol only works when components can be targeted with unambiguous selectors. There are several approaches to dealing with this (very minor) challenge.

Extend the component configuration #

The obvious solution is to add the enlarged button to the component configuration with a more specific selector than the selector that targets the basic button:

[
  {
    "source": ".basic-button",
    "target": ".btn, .button",
    "name": "Basic button"
  },
  {
    "source": ".basic-button.large",
    "target": ".btn-large, .button-large",
    "name": "Larger button"
  }
]

If you think about it, this should not help, but in fact it does! The existence of another component with the selector .basic-button.large does not prevent the a component with both classes from also being matched by .basic-button, but Warhol performs its component matches in decreasing order of specificity. If a component candidate (an element that matches any component selector) has been matched by a high-specificity selector, it gets removed from the pool of component candidates and can no longer clash with component definitions that use a selector with lower specificity.

Use attribute selectors #

Another easy way out of this problem is to use classes not as CSS classes, but with attribute selectors instead:

[
  {
    "source": "[class=basic-button]",
    "target": "[class=btn], [class=button]",
    "name": "Basic button"
  }
]

This will now only match component elements with a class attribute exactly equal to basic-button - no extensions allowed! This way is not really how you would write idiomatic CSS selectors, but in our case, the flexibility that comes from idiomatic CSS selectors is just what we do not want.

Use individual component URLs #

Your pattern library does not have to be huge kitchen sink where all components live on one single page. Every component can have its own page, which can help with ambiguity in the pattern library. Note that ambiguity in the production web page can cause Warhol to misidentify components, even when they are segregated in the pattern library.

Join our Slack Community

Any question not answered yet? You want to get the latest information on Warhol and discuss new features? Join our Slack Community.