Skip to content

Using Design System

Jan Václavík edited this page Nov 13, 2024 · 1 revision

Welcome to the trezor-suite wiki!

DS = design system

Storybooks

Components

image

@trezor/product-components

  • A package with UI components that are product-oriented and can be shared across Suite, Connect, etc.
  • In their implementation, they usually use simpler primitives from @trezor/components

@trezor/components

  • Design primitives (a.k.a. LEGO building blocks) from which the rest of the application is composed.
  • More complex product-components are built from these components.

Using components

Don’t override components

  • We do not override components from @trezor/components
  • If necessary, we wrap them with our own wrapper component
  • If it makes sense, we request a modification or extension of the DS component in [@suite_usability_feedback](https://satoshilabs.slack.com/archives/C07NNMUBJFN)
  • We have custom eslint rule that guards not overring components from @trezor/components and @trezor/product-components . Please neve use eslint ignore for this rule even though we have many of these suppressions in the app.
// BAD ❌
const StyledButton = styled(Button)`
    margin-left: 8px;
`;
...
<StyledButton>Hey!</StyledButton>

// SEMI-GOOD 🟠
// This solution is 100% good in the case when component doesn't allow you to change desired property (which is not case of `margin-left`)
const StyledButton = styled.div`
    margin-left: 8px;
`;
...
<Wrapper>
  <Button>Hey!</Button>
</Wrapper>

// GOOD ✅
<Button margin={{ left: 8 }}>Hey!</Button>

// BEST ✅ ✅
import { spacings } from '@trezor/theme';
...
<Button margin={{ left: spacings.xs }}>Hey!</Button>

Adding new components

Layout

  • For layout, we use components like Row, Column, Table, Grid, and List
  • For spacing, we use the gap prop
  • Layout components support various props from frameProps, typically margin, width, or height . Typically you can find allowed props in storybook or types.
  • Where possible, we use semantic HTML elements (e.g., section, header) for custom components
// BAD ❌
const Row = styled.div`
  display: flex;
  flex-direction: row;
  align-items: space-between;
  margin-top: 8px;
  margin-bottom: 4px;
`;
...
<Row>
  <div>Hola</div>
  <div>Hey</div>
</Row>

// GOOD ✅
import { Row } from '@trezor/components';
...
<Row alignItems="space-between" margin={{ top: spacings.xs, bottom: spacings.xxs }}>
  <div>Hola</div>
  <div>Hey</div>
</Row>

Design tokens / CSS

How to identify legacy stuff

There are many legacy tokens in the app. We are continuously refactoring them but it takes time. In the new functionality, we try to avoid them if possible. You can typically recognize legacy component with following 3 characteristics:

  • UPPER CASE syntax

    • Example: variables.FONT_SIZE.TINY
  • it has legacy in the name

    • Example theme.legacy.TYPE_LIGHT_GREY
  • IDE shows it’s deprecated (usually it’s strike-through)

    • Example

    image.png

// BAD ❌
font-size: ${variables.FONT_SIZE.TINY};
color: ${({ theme }) => theme.legacy.TYPE_LIGHT_GREY};

// GOOD ✅
${typography.label};
color: ${({ theme }) => theme.textSubdued};

Component Colors via variant

  • For most DS components where it makes sense, it's possible to set a color. Primarily, we always use the variant prop, which sets the color according to predefined design variants. Sometimes it might be necessary to use a different color that isn't defined in the variants. In such cases, the color prop can be used, though it is generally considered deprecated.
  • The variants provide basic color settings: primary, secondary, tertiary, info, warning, destructive.

Typography

  • For all text elements, it is possible to set different typography. In the application, we have a predefined list of all used combinations, and we always use them in these combinations. You can choose from the following options:
// packages/theme/src/typography.ts

const typographyStylesBase = {
    titleLarge: {
        fontSize: 48,
        lineHeight: 53,
        fontWeight: fontWeights.medium,
        letterSpacing: 0.4,
    },
    titleMedium: {
        fontSize: 34,
        lineHeight: 37,
        fontWeight: fontWeights.medium,
        letterSpacing: 0.4,
    },
    titleSmall: {
        fontSize: 22,
        lineHeight: 32,
        fontWeight: fontWeights.medium,
        letterSpacing: -0.3,
    },
    highlight: {
        fontSize: 16,
        lineHeight: 24,
        fontWeight: fontWeights.semiBold,
        letterSpacing: -0.4,
    },
    body: {
        fontSize: 16,
        lineHeight: 24,
        fontWeight: fontWeights.medium,
        letterSpacing: -0.4,
    },
    callout: {
        fontSize: 14,
        lineHeight: 20,
        fontWeight: fontWeights.semiBold,
        letterSpacing: -0.3,
    },
    hint: {
        fontSize: 14,
        lineHeight: 20,
        fontWeight: fontWeights.medium,
        letterSpacing: -0.3,
    },
    label: {
        fontSize: 12,
        lineHeight: 18,
        fontWeight: fontWeights.medium,
        letterSpacing: -0.1,
    },
}

For all text, we use the Text or Paragraph components. Paragraph is a block-level text element and internally uses the Text component. These components also allow setting common font properties:

  • variant: Sets the text color based on the variant (cannot be used together with the color prop).
  • ~~color~~: (deprecated) allows setting a specific text color. Cannot be used with variant. Instead, the variant property should always be used.
  • typographyStyle: basically style from list above
  • textWrap: you can set how text should be wrapped, for example for not having orphans in the last line

Example:

// BAD: custom typography ❌
const CustomText = styled.div`
  font-size: 14px;
`
const CustomParagraph = styled.p`
  font-size: 14px;
`

<CustomText>I love Solana</CustomText>
<CustomParagraph>I love Bitcoin even more</CustomParagraph>

// BAD: using deprecated tokens ❌

const CustomText = styled.div`
  font-size: ${variables.FONT_SIZE.TINY};
`
const CustomParagraph = styled.p`
  font-size: ${variables.FONT_SIZE.TINY};
`

<CustomText>I love Solana</CustomText>
<CustomParagraph>I love Bitcoin even more</CustomParagraph>

// GOOD ✅
<Text typographyStyle="callout">I love Solana</Text>
<Paragraph typographyStyle="callout">I love Bitcoin even more</Paragraph>

Using design tokens

We try to use design tokens from @trezor/theme as much as possible. It improves consistency in the app and it’s also easier to refactor. We don’t allow non-standard values.

  • Spacings

    For all sizes in general (especially margins and paddings) we always use predefined sizes from spacings or spacingsPx.

    Example:

    //
    // BAD ❌
    //
    
    // using in CSS ❌
    const CustomComponent = styled.div`
    	padding: 12px;
    `
    
    // Not using spacingsPx ❌
    const CustomComponent = styled.div`
    	padding: ${spacings.md}px;
    `
    
    // Using in prop  ❌
    <Row margin={{ top: 123 }} gap={123}>
      ...
    </Row>
    
    //
    // GOOD  ✅
    //
    
    import { spacings, spacingsPx } from '@trezor/theme';
    
    // using in CSS ✅
    const CustomComponent = styled.div`
    	padding: ${spacingsPx.md};
    `
    // Using in prop ✅
    <Row margin={{ top: spacings.md }} gap={spacings.md}>
      ...
    </Row>
  • Borders

    We use predefined border radii and widths. You can find current options here packages/theme/src/borders.ts .

    image

    Example of using borders:

    // BAD: use incorrect values and without tokens ❌
    const Box = styled.div`
    	border-radius: 6px;
    	border: solid 0.5px red;
    `
    
    // BAD: use correct values, but without tokens ❌
    const Box = styled.div`
    	border-radius: 8px;
    	border: solid 1px red;
    `
    
    // GOOD  ✅
    import { borders } from '@trezor/theme';
    
    ...
    
    const Box = styled.div`
    	border-radius: ${borders.radii.xs};
    	border: solid ${borders.widths.small} red;
    `
  • Z-indices

  • boxShadows

  • Icons

    We should use only icons from our iconset. Ideally we shouldn’t use icons that are marked as deprecated. Iconset can be found [here](https://dev.suite.sldev.cz/components/develop/?path=/story/icons--all-icons).

Use $ for parameters in styled-components

  • more info here
  • odkaz na figmu s barvama

Desktop app shares color tokens with Mobile app. This means we can’t simply change them without being aligned together. In case we need a new color we don’t add it to the theme but ideally we should ask in the Slack channel [suite_usability_feedback](https://satoshilabs.slack.com/archives/C07NNMUBJFN)) why it’s missing.

image

Elevations

In a design system, elevation refers to the visual hierarchy created by applying shadows or layering effects to elements, giving them a sense of depth and prominence. It helps to differentiate between components based on importance or interaction level, typically using predefined shadow styles or z-index values.

How to use elevation for background:

// BAD: using elevated color but not respecting current elevations ❌
const Container = styled.div`
    background: ${({ theme })=> theme.backgroundSurfaceElevation0};
`;

// GOOD  ✅
import { useElevation } from '@trezor/components';
import { mapElevationToBackground, Elevation } from '@trezor/theme';

// Improvement: `${({ theme, $elevation }) => mapElevationToBackground({ theme, $elevation })}` can be simplified to: `${mapElevationToBackground}`;
const Container = styled.div<{ $elevation: Elevation }>`
    background: ${({ theme, $elevation })=> 
      mapElevationToBackground({ theme, $elevation })};
`;

const Component = () => {
	const { elevation } = useElevation();

	return (
	  <Container $elevation={elevation} />
	)
}

It works the same also for border:

import { mapElevationToBorder } from '@trezor/theme';

...

border: solid 1px ${mapElevationToBorder};

You can rise or lower elevation with ElevationUp and ElevationDown components:

<Container> 
  <ElevationUp>
    This context has higher elevation
  </ElevationUp> 
  <ElevationDown>
    This context has lower elevation
  </ElevationDown> 
</Container>

Code review

  • screenshots nebo dobrý popis
  • maintain PR without growing too big. Ideally less than 500 changed lines. Otherwise it’s really hard to go through it
  • Add screenshots to your PR if it’s related to UI. Ideally two screenshots - before and after state and describe or draw if something is necessary to explain.
  • přidat odkaz na figmu
  • Ask for codereview here: [suite_usability_feedback](https://satoshilabs.slack.com/archives/C07NNMUBJFN)

FAQ

Who to ask

Design system questions:

Design questions:

Product questions: