Migrating to v1
Particle v1 makes the web implementation of dscout's design system more flexible, standardizes conventions and practices, cleans out some cruft, and improves some long-standing pain points. Here's a high-level summary of the changes
The CSS has been largely rewritten with the following in mind:
- Supporting themes
- Standardizing naming conventions
- Maximizing composability, which involves:
- Minimizing specificity
- Minimizing assumptions about how components and HTML elements will be composed
- Removing overly-opinionated global styles
Some enums and scales have been redefined to provide more flexibility, improve clarity, and support themeing. For example:
- Dimension and spacing enums (e.g. width, height, margin, padding, etc) follow a new numeric scale that supports more fine-grained sizes on the small end of the scale.
- Grayscale colors better convey their order from light to dark and are uncoupled from use cases.
Some components that were superfluous or not useful have been removed. Others were updated to be more flexible and ergonomic.
See the changelog for a complete record of changes.
New features
Themes
Particle now supports theming via CSS variables. Components define and apply CSS variables, and themes change those variables to customize the presentation of Particle components. Leveraging CSS variables for theming enables the customization of Particle components without increasing the specificity of their CSS selectors.
Particle-provided themes are applied via the <ThemeProvider />
component. Themes can be nested in any combination.
Color schemes
In conjunction with theming, different color schemes can be defined via CSS variables. Most notably, interfaces can be built in a dark color scheme by simply changing a prop on the <ThemeProvider />
.
info
As of v1.0.0-alpha.4
, color schemes are very much a work in progress. They're ready for experimentation but not yet stable enough for production.
More color variants
Non-grayscale colors now come with dark and light variants.
Input groups
Inputs can be combined with text and buttons in a horizontal layout in any combination by composing buttons and inputs within input group components. See Storybook to learn more.
Spacing utility classes
You can now quickly apply equal margins between sibling elements by applying a spacing utility class to their parent. See documentation to learn more.
Breaking changes
Sass variables
Sass variables have been removed from Particle and replaced with CSS variables (also known as CSS custom properties). Remove any ~@dscout/particle/styles/variables
imports and reference variables via the CSS custom properties syntax:
- color: $color--dscout;
+ color: var(--color--dscout);
- box-shadow: $shadow--l;
+ box-shadow: var(--shadow--l);
info
With Sass variables we were able to pass a color variable to the rgba
CSS function because Sass would expand the color to its RGB values. This doesn't work with CSS variables, but variables have been defined with the RGB values for this use case:
- background-color: rgba($color--coachmark, 0.6);
+ background-color: rgba(var(--color--coachmark-rgb), 0.6);
Grayscale colors
Grayscale colors have been renamed to better communicate their gradation from light to dark. They also no longer apply opacity so that we have more control over exact color contrast ratios.
- main-light
+ gray-1
- dem
+ gray-2
- disabled
+ gray-3
- line
+ gray-4
- faint
+ gray-5
- min
+ gray-6
Numeric dimension enums
The previous enums for dimension utility classes (e.g. width--4
) represented multiples of the "base" size (2em
). They now directly correlate to ems.
To replace numeric dimension utility classes, simply double the number.
- width--10
+ width--20
tip
It's recommended to refactor numeric dimension enums before you refactor t-shirt size enums so that you don't mistakenly double the t-shirt sizes.
T-shirt size enums for padding, margin, dimensions, and positioning
All padding, margin, dimension, and positioning enums are now named after their em
values, not t-shirt sizes.
- padding--xs
+ padding--0.25
- margin--s
+ margin--0.75
- width--m
+ width--1
- height--base
+ height--2
- max-width--l
+ max-width--3
- max-height--xl
+ max-height--4
- top--m
+ top--1
- left--base
+ left--2
info
All $spacing--
variables have been removed (e.g. $space--m
, $space--base
, $space--10
, etc). Replace those with their equivalent em
values in CSS.
<Modifier />
spacing and dimension enums
The <Modifier />
props padding
, margin
, height
, width
, and maxWidth
expect the same em-based enums as the equivalent utility classes.
- <Modifier margin={{ bottom: 'm' }} padding="base">
+ <Modifier margin={{ bottom: '1' }} padding="2">
- <Modifier height="base" width="11">
+ <Modifier height="2" width="22">
<Separator />
size enums
The <Separator />
size
prop also no longer accepts t-shirt size enums. It accepts the same em-based enums as margin utility classes:
- <Separator size="xs" />
+ <Separator size="0.25" />
- <Separator size="base" />
+ <Separator size="2" />
Similarly, the <hr />
element can no longer be styled with t-shirt sized class names. Use a margin utility class or use the <Separator />
component.
- <hr className="none" />
+ <Separator size="none" />
- <hr className="xs" />
+ <Separator size="0.25" />
- <hr className="base" />
+ <Separator size="2" />
- <hr className="xl" />
+ <Separator size="4" />
<ButtonPrimary />
and <ButtonSecondary />
These have been removed. They were just preconfigurations of the <Button>
component and can be replaced with the <Button>
.
- <ButtonPrimary>
+ <Button color="dscout" variant="shadow">
- <ButtonPrimary alternate>
+ <Button variant="glass">
- <ButtonPrimary dismiss>
+ <Button color="none">
- <ButtonSecondary priority="1">
+ <Button size="small">
- <ButtonSecondary priority="2">
+ <Button size="small variant="outline">
- <ButtonSecondary priority="3">
+ <Button size="small" color="none">
<ButtonGroup />
This has been removed. It can be replaced with a simple flex layout and, if desired, a top margin.
- <ButtonGroup>
+ <Modifier margin={{ top: '4' }}>
+ <Flex spacing="0.75">
+ </Modifier>
- <ButtonGroup align="center">
+ <Modifier margin={{ top: '4' }}>
+ <Flex spacing="0.75" justify="center">
+ </Modifier>
- <ButtonGroup align="right">
+ <Modifier margin={{ top: '4' }}>
+ <Flex spacing="0.75" justify="end">
+ </Modifier>
<ButtonLink />
This has been renamed to <ButtonPlain />
to better reflect what it actually is. It was never styled like a link, it just removed all button styles.
- <ButtonLink>
+ <ButtonPlain>
<ButtonIcon />
The <ButtonIcon />
previously had more of a "declarative" API, describing its state but not how it looked based on that state. This API didn't support the range of use cases for this component and has been replaced with a more "imperitive" API that spells out the color, background color, and background size in the default and hover states.
If desired, this component could always be wrapped in a component that exposes a more "declarative" API via props and configures the <ButtonIcon />
props accordingly.
- <ButtonIcon active>
+ <ButtonIcon color="dscout">
- <ButtonIcon affirmative>
+ <ButtonIcon hoverColor="success">
- <ButtonIcon affirmative active>
+ <ButtonIcon color="success">
- <ButtonIcon destructive>
+ <ButtonIcon hoverColor="alert">
- <ButtonIcon destructive active>
+ <ButtonIcon color="alert">
<Controls />
and <ButtonIcon />
The <ButtonIcon />
had additional styles and behaviors when composed in a <Controls />
component. The <Controls />
component has been removed and can be replaced with a <ButtonIcon />
in a flex layout:
- <Controls>
- <ButtonIcon>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="2.5em"
+ hoverBgColor="gray-4"
+ hoverBgSize="3.5em"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon active>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="2.5em"
+ color="dscout"
+ hoverBgColor="dscout-light"
+ hoverBgSize="3.5em"
+ hoverColor="dscout-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon selected>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="3.5em"
+ bgColor="dscout-light"
+ color="dscout-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon destructive>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="2.5em"
+ hoverBgColor="gray-5"
+ hoverBgSize="3.5em"
+ hoverColor="alert-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon destructive active>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="2.5em"
+ color="alert"
+ hoverBgColor="gray-5"
+ hoverBgSize="3.5em"
+ hoverColor="alert-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon destructive selected>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgColor="alert-light"
+ bgSize="3.5em"
+ color="alert-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon affirmative>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="2.5em"
+ hoverBgColor="gray-5"
+ hoverBgSize="3.5em"
+ hoverColor="success-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon affirmative active>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgSize="2.5em"
+ color="success"
+ hoverBgColor="gray-5"
+ hoverBgSize="3.5em"
+ hoverColor="success-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
- <Controls>
- <ButtonIcon affirmative selected>
- <IconWhatever />
- </ButtonIcon>
- </Controls>
+ <Flex height="1.5" justify="end" spacing="0.75">
+ <ButtonIcon
+ bgColor="success-light"
+ bgSize="3.5em"
+ color="success-darkest"
+ >
+ <IconWhatever />
+ </ButtonIcon>
+ </Flex>
<Container />
and <ContainerInner />
The <ContainerInner />
component has been removed. All it did was apply padding and remove its bottom margin. Padding is instead applied to the <Container />
directly (defaulting to 2em
). It can be customized to any padding enum needed via the padding
prop.
- <Container>
- <ContainerInner>
- Some content
- </ContainerInner>
- </Container>
+ <Container>
+ Some content
+ </Container>
- <Container>
- Some content
- </Container>
+ <Container padding="none">
+ Some content
+ </Container>
The <Container />
previously applied both top and bottom margins, with CSS to remove the top margin if the container was the first-child of its parent. This created specificity problems and wasn't always the desired behavior. The <Container />
no longer applies any special margin. Use a vertical spacing utility class (e.g. spacing-v--1
) or the <Modifier />
component to apply vertical margins if needed.
<Bug />
This has been renamed to <Chip />
because who likes to explicitly add bugs to their code? The <Chip />
also now accepts any color enum. If no color
prop is specified it will set colors based on the inherited text color. It also accepts a new textColor
prop to set the text color independently from the background or outline color.
- <Bug>
+ <Chip>
The <Bug />
supported a few color aliases that have been removed in favor of Particle's standard color enums. Replace the following:
- <Bug color="default" />
+ <Chip color="main" />
- <Bug color="draft" />
+ <Chip color="gray-1" />
- <Bug color="open" />
+ <Chip color="success" />
- <Bug color="closed" />
+ <Chip color="close" />
Anchor tags
Anchor tags (<a>
) are no longer styled globally. Global styles for the border and hover behavior caused specificity issues in any other component that rendered an <a>
element with different styling concerns.
Use the <LinkText />
to render a styled link within text content. If you need to render a styled link in a custom link component or in a place that does not support JSX, apply the Link-Text
CSS class.
- <a href="someurl">Click here</a> to see something cool.
+ <LinkText href="someurl">Click here</LinkText> to see something cool.
tip
If you set target="_blank"
on the <LinkText />
component it will automatically apply rel="noopener"
for security.
<Fieldset />
The <Fieldset />
component has been removed. It did very little for us other than apply invalid
styles to form inputs via nested CSS selectors and apply a 2em bottom margin. The invalid
concern is now handled by form input components themselves. Bottom margins can be applied via utility classes as needed, or a vertical spacing utility class (e.g. spacing-v--2
) can be applied to a parent element.
info
Because the <Fieldset />
was the only way to style invalid inputs, it was used frequently even when a <fieldset />
was not the correct semantic element. Unless a <Fieldset />
was wrapping a group of related inputs described by a <legend />
, it should just be replaced with a <div />
.
<aside />
The <aside />
element is no longer globally styled. Replace it with a <Footnote />
.
<AttributeSelector />
The <AttributeSelector />
now renders radio inputs to provide a more accessible interface. This means that a unique name
attribute is now required in order to correctly group the radio inputs. This change may not be noticeable when only one <AttributeSelector />
is rendered at a time, but it will break functionality in forms with multiple <AttributeSelectors />
.
info
For the sake of a11y, an <AttributeSelector />
needs to be labeled via a <legend>
element and wrapped in a <fieldset>
.
Bold utility class and enum
The font-weight--b
utility class has been renamed to font-weight--bold
and the strong
utility class has been removed in favor of using font-weight--bold
.
The <Heading />
component's weight
prop also now requires the bold
enum instead of b
.
- <p className="strong">Bold text</p>
+ <p className="font-weight--bold">Bold text</p>
- <span className="font-weight--b">Bold text</span>
+ <span className="font-weight--bold">Bold text</span>
- <Heading level="1" weight="b">Bold heading</Heading>
+ <Heading level="1" weight="bold">Bold heading</Heading>
onPrefixClick
and onSuffixClick
Input components, such as <TextInput />
, previously accepted an onPrefixClick
and onSuffixClick
callback to make the prefix or suffix clickable. However, the prefix and suffix didn't have the visual affordances of a button, making this interaction unclear.
These props have been removed in favor of composing an input and a button via the <InputGroup />
. Input groups are more verbose but far more flexible.
- <TextInput suffix="Copy" onSuffixClick={() => doSomething()} />
+ <InputGroup>
+ <InputGroupItem grow="1>
+ <TextInput />
+ </InputGroupItem>
+ <InputGroupItem>
+ <InputAddon>
+ <Button variant="glass" width="tight" onClick={() => doSomething}>
+ Copy
+ </Button>
+ </InputAddon>
+ </InputGroupItem>
+ </InputGroup>
<ValidationMessage />
The <ValidationMessage />
component has been removed because it was overly opinionated about how validation messages should be laid out, and those opinions didn't match our actual design patterns. Lay out a <div>
however you'd like and apply the following utility classes to match the original visual treatment:
- <ValidationMessage>You messed up!</ValidationMessage>
+ <div className="font-size--s color--alert">You messed up!</div>
info
For the sake of a11y the error message should be tied to the form element it describes:
<div>
<TextInput name="username" aria-describedby="username-error" />
<div className="font-size--s color--alert" id="username-error">
Bad username!
</div>
</div>
Other miscellaneous breaking changes
All Sass mixins have been removed
Particle component classes have been renamed following a new naming convention. Any custom components that applied Particle component classes (such as menus, buttons, or form elements) will need to be updated. Any CSS overrides will need to be updated (or ideally removed entirely).
Sibling button components no longer apply a right margin by default (because not every instance of sibling buttons are laid out horizontally). Wrap in
spacing-h--0.75
to recreate the original spacing, or apply margin utility classes to individual buttons as needed.The
.display--f
utility class used to also applyalign-items: center
. It now only applies thedisplay
property. Replace with.flex
if you wish to retain the alignment.Input components used to render the input element wrapped in a
<div>
. That<div>
has been removed and should have no impact in most instances, but some layouts may have been implicitly relying on that wrapping block element. One example is components laying out a label and an input in a flex layout, e.g:<Flex justify="between">
<label htmlFor="someId">Label</label>
<TextInput id="someId" />
</Flex>Without the wrapping div the text input may fill more horizontal space than before. Wrap the
<TextInput />
in<div>
to restore the previous layout.Deprecated directional margin and padding utility class names (ones that incorrectly applied hyphens according to utility class naming conventions) have been removed for good:
- <div className="margin--b-none">
+ <div className="margin-b--none">
- <div className="padding--h-base">
+ <div className="padding-h--2">The
<List />
component class (previouslylist
, nowList
) used to remove padding by default. Now only theList--plain
class does. Any custom component that used the Particle<List />
component class directly will need to be updated to remove the padding.The
<Modal />
padding
prop now accepts a padding enum, not a boolean. If a boolean was being passed by the consuming component, it should be replaced withundefined
(or the prop should be removed) in order to preserve the default padding of 2ems.The
<ContainerHeader />
now requires anisEditing
prop to style its editing state. It used to rely on nested CSS selectors from the parent<EditingContainer />
, which undesirably increased specificity in its styles.