Back to Styled Components

Typescript

sections/api/typescript.mdx

latest5.6 KB
Original Source

TypeScript | v6

styled-components provides TypeScript definitions which empowers the editing experience in IDEs and increases type safety for TypeScript projects.

For older versions of styled-components, there are community definitions available via the @types/styled-components NPM package.

Create a declarations file

TypeScript definitions for styled-components can be extended by using declaration merging since version v4.1.4 of the definitions.

So the first step is creating a declarations file. Let's name it styled.d.ts for example.

ts
// import original module declarations
import 'styled-components';

// and extend them!
declare module 'styled-components' {
  export interface DefaultTheme {
    borderRadius: string;

    colors: {
      main: string;
      secondary: string;
    };
  }
}

React-Native:

ts
import 'styled-components/native'

declare module 'styled-components/native' {
  export interface DefaultTheme {
    borderRadius: string;

    colors: {
      main: string;
      secondary: string;
    };
  }
}

DefaultTheme is being used as an interface of props.theme out of the box. By default the interface DefaultTheme is empty so that's why we need to extend it.

Create a theme

Now we can create a theme just by using the DefaultTheme declared at the step above.

ts
// my-theme.ts
import { DefaultTheme } from 'styled-components';

const myTheme: DefaultTheme = {
  borderRadius: '5px',

  colors: {
    main: 'cyan',
    secondary: 'magenta',
  },
};

export { myTheme };

Styling components

That's it! We're able to use styled-components just by using any original import.

tsx
import styled, { createGlobalStyle, css } from 'styled-components';

// theme is now fully typed
export const MyComponent = styled.div`
  color: ${props => props.theme.colors.main};
`;

// theme is also fully typed
export MyGlobalStyle = createGlobalStyle`
  body {
    background-color: ${props => props.theme.colors.secondary};
  }
`;

// and this theme is fully typed as well
export cssHelper = css`
  border: 1px solid ${props => props.theme.borderRadius};
`;

Using custom props

If you are adapting the styles based on props, and those props are not part of the base tag / component props, you can tell TypeScript what those extra custom props are, with type arguments like this (TypeScript v2.9+ is required):

tsx
import styled from 'styled-components';
import Header from './Header';

interface TitleProps {
  readonly $isActive: boolean;
}

const Title = styled.h1<TitleProps>`
  color: ${(props) => (props.$isActive ? props.theme.colors.main : props.theme.colors.secondary)};
`;

Note: if you style a standard tag (like <h1> in above example), styled-components will not pass the custom props (to avoid the Unknown Prop Warning).

However, it will pass all of them to a custom React component:

tsx
import styled from 'styled-components';
import Header from './Header';

const NewHeader = styled(Header)<{ customColor: string }>`
  color: ${(props) => props.customColor};
`;
// Header will also receive props.customColor

If the customColor property should not be transferred to the Header component, you can leverage transient props, by prefixing it with a dollar sign ($):

tsx
import styled from 'styled-components';
import Header from './Header';

const NewHeader2 = styled(Header)<{ $customColor: string }>`
  color: ${(props) => props.$customColor};
`;
// Header does NOT receive props.$customColor

Depending on your use case, you can achieve a similar result by extracting the custom props yourself:

tsx
import styled from 'styled-components';
import Header, { Props as HeaderProps } from './Header';

const NewHeader3 = styled(({ customColor, ...rest }: { customColor: string } & HeaderProps) => <Header {...rest} />)`
  color: ${(props) => props.customColor};
`;

Or using shouldForwardProp:

tsx
import styled from 'styled-components';
import Header from './Header';

const NewHeader4 = styled(Header).withConfig({
  shouldForwardProp: (prop) => !['customColor'].includes(prop),
})<{ customColor: string }>`
  color: ${(props) => props.customColor};
`;

Caveat with className

When defining a component you will need to mark className as optional in your Props interface:

tsx
interface LogoProps {
  /* This prop is optional, since TypeScript won't know that it's passed by the wrapper */
  className?: string;
}

class Logo extends React.Component<LogoProps, {}> {
  render() {
    return <div className={this.props.className}>Logo</div>;
  }
}

const LogoStyled = styled(Logo)`
  font-family: 'Helvetica';
  font-weight: bold;
  font-size: 1.8rem;
`;

Caveat with Function Components

To use function components and have typechecking for the props you'll need to define the component alongside with its type. This is not special to styled-components, this is just how React works:

tsx
interface BoxProps {
  theme?: ThemeInterface;
  borders?: boolean;
  className?: string;
}

const Box: React.FunctionComponent<BoxProps> = (props) => <div className={props.className}>{props.children}</div>;

const StyledBox = styled(Box)`
  padding: ${(props) => props.theme.lateralPadding};
`;