How To Make Text Component with React, TypeScript and Styled Components
Tags: react, ui, typescript
Published: 2023-07-01 11:56:13

✨ [**Watch on YouTube**](https://youtu.be/vtqVjskVe1s)  |  🐙 [GitHub](https://github.com/RodionChachura/reactkit)  |  🎮 [Demo](https://reactkit.radzion.com/text)

Text is likely the most used component in any app. Today I want to share a solid reusable Text component that is very useful in my front-end development work and should save time for you too.

## Text Component

The `Text` component is just a styled paragraph that we can also turn into any other element like div, span, or heading by passing the `as` property. Also, we can modify it any way we like, using the `styles` property or wrapping it with the `styled` function. Because of that flexibility, it serves as a building block for other components.

```tsx
import styled, { DefaultTheme, css } from "styled-components"

const getTextColorRecord = ({ colors }: DefaultTheme) =>
  ({
    regular: colors.text,
    supporting: colors.textSupporting,
    supporting2: colors.textSupporting2,
    supporting3: colors.textSupporting3,

    primary: colors.primary,
    attention: colors.attention,
    alert: colors.alert,
    success: colors.success,
    reversed: colors.background,
    contrast: colors.contrast,
  } as const)

type TextWeight = "regular" | "semibold" | "bold"
const fontWeight: Record<TextWeight, number> = {
  regular: 400,
  semibold: 500,
  bold: 600,
}

type TextHeight = "small" | "regular" | "large"
const lineHeight: Record<TextHeight, number> = {
  small: 1,
  regular: 1.2,
  large: 1.5,
}

export type TextColor = keyof ReturnType<typeof getTextColorRecord>

export interface TextProps {
  color?: TextColor
  weight?: TextWeight
  size?: number
  height?: TextHeight
  centered?: boolean
  cropped?: boolean
  nowrap?: boolean
}

export const oneRemInPx = 16

const getFontSize = (sizeInPx: number) => {
  const sizeInRem = sizeInPx / oneRemInPx

  return `${sizeInRem}rem`
}

export const Text = styled.p<TextProps>`
  margin: 0;
  padding: 0;
  overflow-wrap: break-word;

  ${({ color, theme }) =>
    color &&
    css`
      color: ${getTextColorRecord(theme)[color].toCssValue()};
    `}
  ${({ weight }) =>
    weight &&
    css`
      font-weight: ${fontWeight[weight]};
    `}
  ${({ height }) =>
    height &&
    css`
      line-height: ${lineHeight[height]};
    `}
  ${({ size }) =>
    size &&
    css`
      font-size: ${getFontSize(size)};
    `}
  ${({ centered }) =>
    centered &&
    css`
      text-align: center;
    `}

  ${({ nowrap }) =>
    nowrap &&
    css`
      white-space: nowrap;
    `}

  ${({ cropped }) =>
    cropped &&
    css`
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    `}
`
```

For UI consistency we reset default browser CSS at the beginning of the `Text` styling by removing margings and padding and setting `overflow-wrap` to `break-word` to prevent text overflow from breaking the layout. Then we continue by handling it's properties.

Let's look into extra properties that we can pass to the `Text` component. While we can set set color through styles or className, most often we will use the `color` property. It's a limited set of named colors, such as regular, supporting, or alert. We access all of them through the `getTextColorRecord` function that in turn relies on the styled-components theme. Note, that all the names describe the color rather that saying ellow or white. That way we can change the color palette and add new themes without changing the `Text` component.

The `weight` property turns `regular`, `semibold` and `bold` into 400, 500, 600. It's usually enough for most cases. But you can also extends it for you situation, andd `thin` or `extrabold` options for example.

The `size` is an interesting and opinionated property. While we pass a pixels as a number it's being converted to rems. For us it's easier to think in pixels. We think of `16px` as a default size, and to make a smaller variant it's more intuitive to pass `14px` rather than `0.875rem`. At the same time we make the app more accessible by converting pixels to rems, so that users can change the font size in their browser settings and the app will adjust accordingly.

The `height` property represents `line-height` and can be `small`, `regular`, or `large`, yet we are rarely going to use it.

While some people prefer having variants for texts, from my experience I found it more practical to rely on properties like `size` and `weight` to create a variety of text styles. Yet, if your project would benefit from having variants, you can add it as a property, and then have a record of styles for each variant.

Finally there are `centered`, `cropped`, and `nowrap` helpers that are self-explanatory.