On Styling
PokéCommunity's use case when it comes to styling and themeing is somewhat unique. Unlike other "themeable" sites that are mostly just light/dark modes, PC has complex themes with full CSS customizability that really change the entire design of the site. Implementing this well presents some challenges.
Because of this requirement, CSS-in-JS is pretty much off the table, because machine-generated class names can't be overridden in per-theme CSS. While most CSS-in-JS libraries do support some degree of theme variables, it doesn't allow the level of flexibility we need.
Instead, we use a modified version of CSS Modules. Because of the requirement that class names not be modified to allow themes to override them, we're not able to use the feature of CSS Modules where it isolates your class names by adding a unique hash.
CSS Modules are still useful though - they allow us to use :export to pass arbitrary values (such as constants) to JavaScript without duplication, and strongly type the imported style object with TypeScript to prevent typo-ing class names. For example...
Without CSS Modules.
// App.scss
.App {
color: red;
}
// App.tsx
const App = () => (
<div className="App"></div>;
)
With CSS Modules.
// App.module.scss
.App {
color: red;
}
// App.tsx
import styles from './App.module.scss';
const App = () => (
<div className={styles.App}></div>
);
Note the different file extension (.module.scss).
===
Our component library is called artboard. It exports all of our generic layout components, and some SCSS utilities such as mixins and functions.
All Artboard components accept HTMLDivElement props in addition to their unique props, so you can pass an additional className to them if you need to tweak them for a specific context. You should always do this instead of overriding the internal Artboard CSS classes in a given context, to avoid specificity wars.
All internal Artboard classes are formatted as ab-{ComponentName}, e.g. ab-Modal. All mixins and functions, except for a few VERY common media query mixins, are formatted as ab-{function-name}.
===
About layout components
As a rule, Artboard does NOT export layout components, such as Row or Column. We did this before, and it turned out messier than intended. The reason is that it's very common for layouts to change responsively, and different layouts often change in different ways depending on the context. Consider the super simple case of a flexbox column that becomes a row on non-mobile screen sizes. There are several obvious ways we could implement this.
-
With CSS-in-JS's responsive props, such as
<Row flexDirection={['column', 'row']}>. This isn't really an option for us without CSS-in-JS, and even if it were, it's semantically kind of weird because we're using<Row>as something it's not. -
Add a custom
classNameto<Row>that makes it a column some of the time. Or vice versa. This has the same semantic issue as #1, and on top of that we now have to deal with CSS specificity issues. -
Making a separate
<RowOrColumn>component. This is arguably acceptable but it's a) long, b) reinventing the wheel, c) moves the task of determining whether to show a row or a column to JS rather than CSS. While it might fly in this simplified example, it's not feasible to do this kind of thing for every common case of changing the layout based on screen size.
Because of this kind of issue, we've taken the approach that Artboard should not export components for simple layouts, because making them customizable enough to handle a variety of use cases requires more trouble than it's worth for something that would take five seconds with regular CSS. That doesn't mean it forces you to write every layout from scratch though! Artboard comes with SCSS utilities to simplify things that don't incur the issues above.
@import "artboard/std/layout.module.scss";
.MyComponent {
@include ab-column($gap: 5px);
@media screen and (min-width: 768px) {
@include ab-row($gap: 5px);
}
}
Because we're doing our layouts in plain CSS (well, SCSS), we don't incur any of the issues above. We can even shorten this further with some other artboard/std helpers.
@import "artboard/std/layout.module.scss";
@import "artboard/std/mediaQuery.module.scss";
.MyComponent {
@include ab-column($gap: 5px);
@include tablet { @include ab-row($gap: 5px) }
}
===
Some random things to keep in mind.
-
Because we don't get the benefit of unique class names with CSS Modules, we use BEM conventions to protect against style conflicts.
-
If you find yourself writing the same CSS a lot, consider if it can be made into an Artboard component! We can never have too many Artboard components.
-
Not every non-Artboard component should need an accompanying CSS file. In fact, the more that don't, the better, because it means Artboard is pulling its weight. However, every component should have a
classNameon its top level HTML element (except forFragments) to act as a hook for themeing, even if it would otherwise go unused.
// no accompanying css file, just using artboard
import Block from 'artboard/Block
const TopicHeader = () => (
<Block className="TopicHeader">
{/* ... */}
</Block>
);
- A decent SCSS styleguide if you're looking for some structure.