import type {CSSProperties} from 'react'; import {Fragment} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import LetterSpacingGraphic from 'sentry-images/stories/typography/letter-spacing.svg'; import LineHeightGraphic from 'sentry-images/stories/typography/line-height.svg'; import WeightGraphic from 'sentry-images/stories/typography/weight.svg'; import {CodeSnippet} from 'sentry/components/codeSnippet'; import {Flex} from 'sentry/components/container/flex'; import ExternalLink from 'sentry/components/links/externalLink'; import Link from 'sentry/components/links/link'; import Panel from 'sentry/components/panels/panel'; import {PanelTable} from 'sentry/components/panels/panelTable'; import {IconCheckmark, IconCircleFill, IconClose} from 'sentry/icons'; import {space} from 'sentry/styles/space'; import type {ColorOrAlias} from 'sentry/utils/theme'; const FixedWidth = styled('div')` max-width: 800px; `; interface TypeScaleItem { fontSize: CSSProperties['fontSize']; fontWeight: CSSProperties['fontWeight']; letterSpacing: CSSProperties['letterSpacing']; lineHeight: CSSProperties['lineHeight']; name: string; } const TYPE_SCALE: TypeScaleItem[] = [ { name: 'Heading 1', fontWeight: 600, fontSize: '2.25rem', lineHeight: 1.2, letterSpacing: '-0.02rem', }, { name: 'Heading 2', fontWeight: 600, fontSize: '1.875rem', lineHeight: 1.2, letterSpacing: '-0.016em', }, { name: 'Heading 3', fontWeight: 600, fontSize: '1.625rem', lineHeight: 1.2, letterSpacing: '-0.012em', }, { name: 'Heading 4', fontWeight: 600, fontSize: '1.375rem', lineHeight: 1.2, letterSpacing: '-0.008em', }, { name: 'Heading 5', fontWeight: 600, fontSize: '1.25rem', lineHeight: 1.2, letterSpacing: '-0.004em', }, { name: 'Heading 6', fontWeight: 600, fontSize: '1.125rem', lineHeight: 1.2, letterSpacing: 'normal', }, { name: 'Paragraph', fontWeight: 400, fontSize: '1rem', lineHeight: 1.4, letterSpacing: 'normal', }, { name: 'Button/Label', fontWeight: 600, fontSize: '1rem', lineHeight: 1.2, letterSpacing: 'normal', }, { name: 'Small', fontWeight: 400, fontSize: '0.875rem', lineHeight: 1.4, letterSpacing: '+0.01rem', }, ]; const InlineLinkExampleStyles = `styled('a')\` color: \${p => p.theme.blue300}; text-decoration: underline; text-decoration-color: ${p => p.theme.blue100}; cursor: pointer; &:hover { text-decoration-color: ${p => p.theme.blue200}; } \`; `; const StandaloneLinkExampleStyles = `/* Link color is flexible, choose between Gray 500, 400, and 300. */ styled('a')\` color: \${p => p.theme.gray500}; text-decoration: none; cursor: pointer; &:hover { text-decoration: underline; text-decoration-color: \${p => p.theme.gray200}; } \`; `; const UnorderedListExampleStyles = `/* First-level items */ ul > li { list-style-type: disc; } /* Second-level items */ ul > ul > li { list-style-type: circle; } `; const OrderedListExampleStyles = `/* First-level items */ ul > li { list-style-type: decimal; } /* Second-level items */ ul > ul > li { list-style-type: lower-alpha; } `; const TabularNumsExampleStyles = `/* Add this to numeric columns */ font-variant-numeric: tabular-nums; `; const FontLigatureExampleStyles = `/* Add this to the root element */ font-feature-settings: 'liga'; `; const FontFractionExampleStyles = `/* Be careful: this changes the appearance of normal, non-fractional numbers, so only apply it to specific text elements with fractions inside. */ font-feature-settings: 'frac'; `; export default function TypographyStories() { return (

Typography

We've built Sentry's type system around Rubik - a playful open-source typeface. For code and code-like elements, we use Roboto Mono.


Type scale

Type scales are hierarchical type systems consisting of style definitions for common elements, such as Heading 1, Heading 2, Paragraph, and Button/Label.

Sentry's type scale is based on the Rubik typeface. The root font size is 16px (1rem = 16px).

{TYPE_SCALE.map(({name, ...props}) => { return (
{name}
{props.fontWeight}
{props.fontSize}
{props.lineHeight}
{props.letterSpacing}
); })}

Styling

The type scale above should cover a large majority of use cases. However, if an element requires a custom style outside of the type scale, make sure to follow the rules below.

Size

Use values from the type scale above. Be mindful of the type hierarchy. If the element has low importance, use a smaller size.

Always define font sizes with the rem unit.

Weight

Line height

Letter spacing


Code

Use Roboto Mono in Regular (400) for code and code-like elements, like search tokens.

Set the line height based on the context:


External Links

External links lead users to pages outside the application. Examples include links to Sentry's blog/marketing pages, terms of service, third-party documentation,…

The following styling rules apply to external links only. Internal links, on the other hand, can have more flexible styles, based on their behavior and context.

In a sentence

When a link appears inside a longer sentence…

... like this{' '} {}}>little link.

{InlineLinkExampleStyles}

Standalone

When a link appears on its own and the user likely knows that it's a link given the context, like in a footer:

Privacy Policy Terms of Use

{StandaloneLinkExampleStyles}


Lists

Unordered

Use filled and hollow circles as bullets points:

{UnorderedListExampleStyles}

Don't add full stops (.) to the end of each item, unless the item contains multiple sentences.

Avoid using custom symbols and icons as bullet characters, as they usually look out of place and distract from the main text content.

Ordered

Use Arabic numerals and lowercase letters as counters:

  1. Camelus
    1. Bactrian camel
    2. Dromedary
  2. Lama
    1. Llama
    2. Alpaca

{OrderedListExampleStyles}

Avoid using custom symbols and icons as counters.


OpenType features

Rubik supports a few useful{' '} OpenType font features . These features, or variants, are alternative characters that, when used in the right places, can help improve the reading experience.

Tabular figures

By default, Rubik uses proportional figures. This works well in most cases. However, for large tables with a lot of numbers, tabular figures would be a better choice, thanks to their consistent width and more legible design.

999,999
999,999
111,111
111,111
9.99999
9.99999
1.11111
1.11111

{TabularNumsExampleStyles}

Ligatures

Ligatures are special glyphs that replace two or more glyphs in order to better connect them. Common ligature replacements include ff, fi, fl, and ffi.

  • ff, fi, fl

    Without ligatures, the characters are all separate.

  • ff, fi, fl

    With ligatures, the characters are connected into a single glyph.

  • Use ligatures across the whole user interface.

    {FontLigatureExampleStyles}

    Fractions
  • 1/12

    Rubik also contains special formatting for fractions. Without this formatting, numbers in fractions are just rendered as separate characters.

  • 1/12

    Fractional formatting shrinks the numbers and connects them with a diagonal slash, forming a proportional, condensed visual block.

  • Use fractional formatting whenever possible.

    {FontFractionExampleStyles}

    ); } const FixedExternalLink = styled(ExternalLink)` color: ${p => p.theme.blue300}; text-decoration: underline ${p => p.theme.blue100}; :hover { color: ${p => p.theme.blue300}; text-decoration: underline ${p => p.theme.blue200}; } `; const FooterLink = styled(Link)` color: ${p => p.theme.gray300}; :hover { color: ${p => p.theme.gray300}; text-decoration: underline ${p => p.theme.gray200}; } `; const SideBySideList = styled('ul')` /* Reset */ list-style-type: none; margin: 0; padding: 0; & > li { margin: 0; } & > li > div { margin-bottom: 0; } /* Side-by-side display */ display: grid; grid-template-columns: 1fr 1fr; gap: ${space(2)}; `; const ColorSwatch = styled( ({ color, className, style, }: { color: ColorOrAlias; className?: string; style?: CSSProperties; }) => ( {color} ) )` display: inline-flex; gap: ${space(0.5)}; align-items: center; border: 1px solid ${p => p.theme.border}; border-radius: ${p => p.theme.borderRadius}; padding: ${space(0.25)} ${space(0.5)}; vertical-align: sub; `; const ExampleImg = styled('img')` border: 1px solid ${p => p.theme.border}; border-radius: ${p => p.theme.borderRadius}; `; const PositiveLabel = styled( ({className, style}: {className?: string; style?: CSSProperties}) => (
    DO
    ) )` color: ${p => p.theme.green400}; align-items: center; display: flex; font-weight: ${p => p.theme.fontWeightBold}; gap: ${space(0.5)}; `; const NegativeLabel = styled( ({className, style}: {className?: string; style?: CSSProperties}) => (
    DON'T
    ) )` color: ${p => p.theme.red400}; align-items: center; display: flex; font-weight: ${p => p.theme.fontWeightBold}; gap: ${space(0.5)}; `; const ExamplePanel = styled(Panel)<{fontSize?: 'large'}>` padding: ${space(2)}; ${p => p.fontSize === 'large' ? css` font-weight: ${p.theme.fontWeightBold}; font-size: 1.875rem; line-height: 1.2; letter-spacing: -0.016em; ` : ''} `; const TabularNum = styled('div')` font-variant-numeric: tabular-nums; `; const FontLiga = styled('div')` /** * TODO: This should be applied to the root node of the side, why is that not the case? */ font-feature-settings: 'liga'; `; const FontNoLiga = styled('div')` /** * Using 'liga' is the default * We want to turn it off for the example. * * Don't copy+paste this! */ font-feature-settings: 'liga' 0; `; const FontFractional = styled('div')` font-feature-settings: 'frac'; `;