Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ root = true
[*]
end_of_line = LF
charset = utf-8
indent_style = space
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
Expand Down
11 changes: 6 additions & 5 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"tabWidth": 4,
"printWidth": 140,
"singleQuote": true,
"proseWrap": "always",
"trailingComma": "all"
"useTabs": true,
"tabWidth": 4,
"printWidth": 140,
"singleQuote": true,
"proseWrap": "always",
"trailingComma": "all"
Comment on lines +2 to +7

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes formatting defaults repo-wide (useTabs: true). That’s a broad, potentially high-churn change that isn’t implied by the PR title (migration guide) and will affect every future formatted file. If the intent is only to fix a single file’s formatting, consider reverting this config change or scoping it to specific filetypes via overrides.

Copilot uses AI. Check for mistakes.
}
6 changes: 3 additions & 3 deletions src/model/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export const categories = [
description: '',
},
{
id: CategoryId.REACT,
title: 'React Ecosystem',
description: 'Thoughts about the React ecosystem, mostly on building declarative UIs and handling state.',
id: CategoryId.FRONTEND,
title: 'Frontend Web Development',
description: 'Thoughts about the frontend web development, including the React ecosystem, CSS libraries and everything in between.',

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammar: “Thoughts about the frontend web development…” reads awkwardly; consider changing to “Thoughts about frontend web development…” (or similar) to avoid the extra “the”.

Suggested change
description: 'Thoughts about the frontend web development, including the React ecosystem, CSS libraries and everything in between.',
description: 'Thoughts about frontend web development, including the React ecosystem, CSS libraries and everything in between.',

Copilot uses AI. Check for mistakes.
},
{
id: CategoryId.TESTING,
Expand Down
138 changes: 138 additions & 0 deletions src/pages/blog/migrating-styled-components-vanilla-extract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: Migrating from styled-components to vanilla-extract
date: 2023-06-04
spoiler: Some learnings after migrating a codebase from styled-components into vanilla-extract.
category: frontend
---

Here are some learnings from migrating a codebase from [styled-components](https://styled-components.com/) to
[vanilla-extract](https://vanilla-extract.style/). Some of the migration steps were not super clear in the official docs, so hopefully
you'll find this useful.

The main motivation for this migration was to move from a model in which styles were generated at runtime to one where they were generated
during build time. For a small and simple codebase this change might not be noticeable, but for a large scale enterprise web application
this could potentially be a significant performance improvement.

## Configuring vanilla-extract to work with Gatsby and Jest

The first step was to configure Gatsby to work with vanilla-extract, which involves installing and adding `gatsby-plugin-vanilla-extract` to
the collection of plugins in the `gatsby-config.js` file.

These are the dependencies I had to install to get this to work:

```
yarn add @vanilla-extract/css @vanilla-extract/jest-transform @vanilla-extract/webpack-plugin gatsby-plugin-vanilla-extract
```

This is all you need to get the tool to work with Gatsby. However, if you try to run your unit tests (in my case, powered by Jest and
Enzyme) they will fail. To fix this you'll need to
[configure vanilla-extract's Babel transformer](https://vanilla-extract.style/documentation/test-environments/#jest) on all `*.css.ts`
files, and make sure this transformer gets called before `babel-jest` as this does not know anything about vanilla-extract, so you need to
transform all style definitions first before moving on to the rest of the code.

You'll also need to
[remove any stubbing of `*.css` files](https://vanilla-extract.style/documentation/test-environments/#remove-style-mocking) as part of the
`moduleNameMapper` map, as this clashes with vanilla-extract because Jest can't differentiate between `.css` and `.css.ts` imports.

This is what the `jest.config.js` file looks like after these changes:

```js {diff}
module.exports = {
transform: {
+ '\\.css\\.ts$': '@vanilla-extract/jest-transform',
'^.+\\.[jt]sx?$': 'babel-jest',
},
moduleNameMapper: {
- '.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`,
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `<rootDir>/__mocks__/file-mock.js`,
},
};
```

There's something else you need to consider regarding whether you need styles to be available while running your tests. While sometimes you
might need to test against your actual styles, it can potentially be a major slowdown. If your tests don't require styles to be available,
you can
[import `disableRuntimeStyles` to prevent all style creation](https://vanilla-extract.style/documentation/test-environments/#disabling-runtime-styles).

```js
import '@vanilla-extract/css/disableRuntimeStyles';
```

Make sure to run your tests and also the `gatsby build` to make sure everything is still working.

Here's the [link to the first commit](https://github.com/fed/blog/commit/69e3b183dca75471a63ad1ecf740f56c4bfa86bb) where I set up the tool.

## Migrating a styled-component with different variants

```ts
export const Lozenge = styled.span<{ children: ReactNode; $type?: LozengeType }>`
border-radius: ${borderRadius.default};
color: ${colors.white};
display: inline-block;
font-family: ${fontFamilies.sansSerif};
font-size: ${fontSizes.xxs};
line-height: ${lineHeights.sm};
padding: ${0.375 * gridSize}px ${0.625 * gridSize}px;
text-transform: uppercase;
white-space: nowrap;

${(props) => {
switch (props.$type) {
case 'primary':
return css`
background-color: ${colors.blue};
`;
case 'success':
return css`
background-color: ${colors.green};
`;
case 'error':
return css`
background-color: ${colors.red};
`;
case 'warning':
return css`
background-color: ${colors.yellow};
color: ${colors.brown};
`;
case 'info':
return css`
background-color: ${colors.purple};
`;
case 'default':
default:
return css`
background-color: ${colors.grayMedium};
`;
}
}}}
`;
Comment on lines +102 to +109

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the styled-components example snippet, the interpolation closing braces look unbalanced (}}}} at the end of the ${(props) => { ... }} block). Since this is a migration guide, it’d be good to fix the snippet so readers can copy/paste it without syntax errors.

Copilot uses AI. Check for mistakes.
```

into the following, which reads nicely:

```ts
import { style, styleVariants } from '@vanilla-extract/css';
import { borderRadius, colors, fontFamilies, fontSizes, gridSize, lineHeights } from '../styles/constants';

const base = style({
borderRadius: borderRadius.default,
color: colors.white,
display: 'inline-block',
fontFamily: fontFamilies.sansSerif,
fontSize: fontSizes.xxs,
lineHeight: lineHeights.sm,
padding: `${0.375 * gridSize}px ${0.625 * gridSize}px`,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
});

export const lozenge = styleVariants({
default: [base, { backgroundColor: colors.grayMedium }],
primary: [base, { backgroundColor: colors.blue }],
success: [base, { backgroundColor: colors.green }],
error: [base, { backgroundColor: colors.red }],
warning: [base, { backgroundColor: colors.yellow, color: colors.brown }],
info: [base, { backgroundColor: colors.purple }],
});
```
59 changes: 30 additions & 29 deletions src/ui/markdown.css.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { style, globalStyle } from '@vanilla-extract/css';

import {
baseParagraphStyleImpl,
baseHeadingStyleImpl,
baseLinkStyleImpl,
baseLinkHoverStyleImpl,
baseFocusStyleImpl,
baseFocusHoverStyleImpl,
baseParagraphStyleImpl,
baseHeadingStyleImpl,
baseLinkStyleImpl,
baseLinkHoverStyleImpl,
baseFocusStyleImpl,
baseFocusHoverStyleImpl,
} from '../styles/common.css';
import { fontFamilies, fontSizes, lineHeights, fontWeights, gridSize, borderRadius } from '../styles/constants';

Expand All @@ -23,55 +23,56 @@ globalStyle(`${markdownStyle} a:focus:hover`, baseFocusHoverStyleImpl);

// Headings
globalStyle(
`${markdownStyle} h1, ${markdownStyle} h2, ${markdownStyle} h3, ${markdownStyle} h4, ${markdownStyle} h5, ${markdownStyle} h6`,
{
...baseHeadingStyleImpl,
fontWeight: fontWeights.bold,
margin: `${6.25 * gridSize}px 0 0`,
},
`${markdownStyle} h1, ${markdownStyle} h2, ${markdownStyle} h3, ${markdownStyle} h4, ${markdownStyle} h5, ${markdownStyle} h6`,
{
...baseHeadingStyleImpl,
fontWeight: fontWeights.bold,
margin: `${6.25 * gridSize}px 0 0`,
},
);

// Bold text
globalStyle(`${markdownStyle} b, ${markdownStyle} strong`, {
fontWeight: fontWeights.bold,
fontWeight: fontWeights.bold,
});

// Images
globalStyle(`${markdownStyle} img`, {
maxWidth: '100%',
maxWidth: '100%',
});

// Add some vertical spacing to all images
globalStyle(`${markdownStyle} .gatsby-resp-image-figure, ${markdownStyle} p > .gatsby-resp-image-wrapper`, {
marginBottom: `${6 * gridSize}px`,
marginTop: `${6 * gridSize}px`,
marginBottom: `${6 * gridSize}px`,
marginTop: `${6 * gridSize}px`,
});

// Figure captions
globalStyle(`${markdownStyle} .gatsby-resp-image-figcaption`, {
...baseParagraphStyleImpl,
fontSize: fontSizes.xs,
marginTop: `${gridSize}px`,
textAlign: 'center',
...baseParagraphStyleImpl,
fontSize: fontSizes.xs,
marginTop: `${gridSize}px`,
textAlign: 'center',
});

// All code
globalStyle(`${markdownStyle} pre, ${markdownStyle} code, ${markdownStyle} kbd, ${markdownStyle} samp`, {
fontFamily: fontFamilies.monospace,
fontFamily: fontFamilies.monospace,
});

// Inline code
globalStyle(`${markdownStyle} code:not([class="grvsc-code"])`, {
backgroundColor: 'rgba(27, 31, 35, 0.05)',
borderRadius: borderRadius.default,
fontSize: '80%',
margin: 0,
padding: `${0.375 * gridSize}px ${0.75 * gridSize}px`,
whiteSpace: 'normal',
fontWeight: fontWeights.normal,
backgroundColor: 'rgba(27, 31, 35, 0.05)',
borderRadius: borderRadius.default,
fontSize: '80%',
margin: 0,
padding: `${0.375 * gridSize}px ${0.75 * gridSize}px`,
whiteSpace: 'nowrap',
fontWeight: fontWeights.normal,
Comment on lines 64 to +71

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline-code style changes whiteSpace from normal to nowrap. This can cause long inline code spans/URLs to overflow their container instead of wrapping, especially on narrow viewports. Consider reverting to normal (or using a wrapping-friendly option like pre-wrap plus overflowWrap/wordBreak) unless the no-wrap behavior is explicitly desired.

Copilot uses AI. Check for mistakes.
});

// Code blocks
globalStyle(`${markdownStyle} pre.grvsc-container`, {
lineHeight: lineHeights.lg,
lineHeight: lineHeights.lg,
tabSize: 4,
});
2 changes: 1 addition & 1 deletion src/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export enum CategoryId {
JAVASCRIPT = 'javascript',
TESTING = 'testing',
ACCESSIBILITY = 'accessibility',

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CategoryId.REACT was removed from the enum, but there are still references to it (e.g. src/model/external-posts.ts:34). This will break the TypeScript build; either add a backwards-compatible alias (e.g. keep REACT = 'react') or update all remaining references to use FRONTEND/'frontend' as intended.

Suggested change
ACCESSIBILITY = 'accessibility',
ACCESSIBILITY = 'accessibility',
REACT = 'react',

Copilot uses AI. Check for mistakes.
REACT = 'react',
FRONTEND = 'frontend',

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming the category id from 'react' to 'frontend' also requires updating test fixtures that assert on the serialized categoryId string (e.g. src/templates/index.test.tsx currently expects categoryId: 'react'). Without updating those, the Jest suite will fail.

Suggested change
FRONTEND = 'frontend',
FRONTEND = 'react',

Copilot uses AI. Check for mistakes.
FRP = 'frp',
}

Expand Down
Loading