diff --git a/contributor-docs/authoring-css.md b/contributor-docs/authoring-css.md new file mode 100644 index 00000000000..1b8512b0fe8 --- /dev/null +++ b/contributor-docs/authoring-css.md @@ -0,0 +1,263 @@ +# Authoring CSS + +Primer React uses [CSS Modules](https://github.com/css-modules/css-modules) for styling. CSS Modules allow us to write component scoped CSS while still authoring in a traditional `.css` file. This guide covers best practices for writing CSS in Primer React. + +## Getting started + +### File setup + +Create a new `.css` file in the same directory as the component you are working on. Name the file the same as the component, and add the extension `.module.css`. + +Example: `Button.modules.css` + +### Importing CSS + +Import the new CSS file into the component TSX file. + +```tsx +import classes from './Button.module.css' +``` + +### Reference CSS classes + +Reference CSS classes in the component TSX file using the `classes` object. + +```css +/* Banner.module.css */ + +.Banner { + background-color: var(--banner-bgColor); +} +``` + +```tsx +// Banner.tsx +import classes from './Button.module.css' +import {clsx} from 'clsx' + +export function Banner({className}) { + return
Banner
+} +``` + +## Code styles + +### CSS classnames + +When component classnames are compiled, they receive a prefix of the component name `prc-{folder}-{local}-` and a suffix of a random hash. + +```css +/* Before compilation */ +.Container { + display: inline-block; +} + +/* After compilation */ +.prc-Button-Container-cBBI { + display: inline-block; +} +``` + +Since classes are prefixed and hashed, the class names themselves can be named generically to represent their intention. + +#### PascalCase + +Use PascalCase for classnames. Additional characters like `-` dashes or `_` underscores must be escaped with a `\` backslash in TSX for the class name to be recognized, which can be cumbersome. + +```css +/* Do */ +.ButtonContent { + display: inline-block; +} + +/* Don't */ +.button-content { + display: inline-block; +} +``` + +#### Pseudo elements + +Prefer using pseudo classes over classnames for state. + +```css +/* Do */ +.Button:disabled { + opacity: 0.5; +} + +/* Don't */ +.ButtonDisabled { + opacity: 0.5; +} +``` + +### `clsx` and className + +Multiple classnames can be referenced on a single node using the `clsx` utility. This is also useful for providing a `className` prop alongside the default class name. + +The `className` prop should only be offered on the top-level element of a component. Avoid offering multiple layers of `className` props to child elements. Consider offering a CSS variable for properties that a consumer may need to customize at the lower levels. + +Ensure that other `...props` are spread before the `className` prop to avoid being overridden. + +```tsx +import {clsx} from 'clsx' + +export function Button({className, ...props}) { + return + ) +} +``` + +### Responsive design + +We utilize PostCSS to allow for CSS variables to be used within media queries. The list of available media queries can be found in the [@primer/primitives viewport documentation](https://primer.style/foundations/primitives/size#viewport). + +To use a viewport variable, write the `@media` rule as normal and place the variable in between the parentheses. + +```css +@media screen and (--viewportRange-regular) { + /* styles */ +} +``` + +### Component prop variants as data-attributes + +When a component has a variant, prefer using a data attribute over a modifier class. + +Some common variants include: + +- data-size +- data-variant +- data-loading + +```css +/* Do */ +.Button:where([data-size='small']) { + height: var(--control-small-size); +} + +/* Don't */ +.ButtonSmall { + height: var(--control-small-size); +} +``` + +Data attributes can be used as a boolean to represent a true or false state, or as a string to represent a specific value. + +```css +/* boolean */ + +.Button:where([data-loading]) { + cursor: not-allowed; +} + +/* string */ + +.Button:where([data-size='small']) { + height: var(--control-small-size); +} +``` + +#### Responsive data attributes + +It is common to offer responsive props that allow the consumer to set styling based on the viewport size. This functionality can be extended via data attributes. + +```tsx +import type {ResponsiveValue} from '../hooks/useResponsiveValue' +import {getResponsiveAttributes} from '../internal/utils/getResponsiveAttributes' + +// types +type PaddingScale = 'none' | 'condensed' | 'normal' | 'spacious' +type Padding = PaddingScale | ResponsiveValue + +// prop +type StackProps = { + padding?: Padding +} + +// component +export function Stack({padding = 'normal'}: StackProps) { + return
+} +``` + +```tsx +// usage + +``` + +By default, we may offer a `padding` prop. The data attribute for `padding` might look like `data-padding="normal"`. To make the `padding` prop responsive, utilize the [ResponsiveValue](https://github.com/primer/packages/react/src/hooks/useResponsiveValue.ts) hook alongside the [getResponsiveAttributes](https://github.com/primer/react/src/internal/utils/getResponsiveAttributes.ts) utility. + +```tsx +// apply the responsive data-attributes using getResponsiveAttributes +export function Stack({padding = 'normal'}: StackProps) { + return
+} +``` + +By using `getResponsiveAttributes`, we can reference data attributes in the CSS file based on the prop type offerings. + +```css +/* Stack.module.css */ +.Stack { + &:where([data-padding='none']), + &:where([data-padding-narrow='none']) { + padding: 0; + } +} +``` + +### Specificity and nesting + +Whenever possible, avoid deep nesting as it creates higher specificity selectors. Rely on `stylelint` to guide how many levels of nesting are acceptable. + +#### Using `:where` to reduce specificity + +The `:where` selector has a specificity of 0, which can be useful for allowing custom overrides. Use the `:where` selector for component options that utilize data-attributes like `&:where([data-size='small'])`. + +### CSS variables + +#### Primer primitives + +Use CSS variables from `@primer/primitives` for size, typography, and color values. Certain components also have their own pattern level CSS variables from `@primer/primitives` that should be used. + +#### Component CSS variables + +CSS variables may also be used contextually to set component variants. These CSS variables are defined within the component CSS file. + +```css +.Banner { + background-color: var(--banner-bgColor); + + &:where([data-variant='critical']) { + --banner-bgColor: var(--bgColor-danger-muted); + } +} +``` + +#### Fallbacks + +Avoid adding fallback values to CSS variables from `@primer/primitives`. These are added automatically and will be compiled to CSS variables with a fallback value. + +### Support + +Prefer CSS features no newer than Baseline 2022. When using CSS features from Baseline 2023 or newer, provide an appropriate fallback for when the feature is unavailable. + +Use `@supports` to target when a specific piece of functionality is not available + +```css +@supports (container-type: inline-size) { + container: banner / inline-size; +} +```