-
Notifications
You must be signed in to change notification settings - Fork 4
Lesson 06: Styling
Speaker: Jan Hoffman
- Pull Request
- Recording (Gdrive)
- Slides (PDF)
- Slides (Powerpoint)
There are many CSS-in-JS solutions, the most popular are Tailwind CSS, Stitches, Emotion, for this course we are using Styled Components.
- install styled components:
yarn add styled-components
- add types:
yarn add --dev @types/styled-components
- update
next.config.js
for nicer classNames:
const nextConfig = {
reactStrictMode: true,
compiler: {
styledComponents: true,
},
}
- if using VS Code, add vscode-styled-components extension to teach it to understand CSS inside template literals (strings).
Create variables for:
- colors
- typography
- media queries
- levels for
z-index
es andbox-shadow
s
Example of colors.ts
:
import { palette } from './palette'
export const colors = {
text: {
base: palette.grey[100],
dimmed: palette.grey[500],
light: palette.grey[700],
formLabel: palette.grey[800],
tabs: palette.grey[600],
inverted: palette.white,
inactive: palette.grey[600],
silent: palette.grey[850],
},
background: {
light: palette.white,
dimmed: palette.grey[900],
dark: palette.grey[100],
inactive: palette.grey[850],
},
accent: {
primary: palette.green,
primaryHover: palette.greenDarker,
destructive: palette.pink,
destructiveHover: palette.pinkDarker,
},
}
Usage in a styled component:
export const CreateLink = styled.a`
position: fixed;
bottom: 0;
right: 0;
width: 5.6rem;
height: 5.6rem;
margin: 3.2rem;
border-radius: 50%;
color: ${colors.text.inverted};
cursor: pointer;
background-color: ${colors.background.dark};
`
Create global styles using createGlobalStyle
:
export const GlobalStyle = createGlobalStyle`
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
}
html,
body, #__next {
padding: 0;
height: 100%;
}
We can then use this GlobalStyle
as a regular component:
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<GlobalStyle />
<HeadDefault />
<Component {...pageProps} />
</>
)
}
Multiple global styles can be used in the app.
px
vs. relative units, do not use html { font-size: 10px; }
, use a relative unit instead (like html { font-size: 62.5%; }
) to make sure our website reflects user's accessibility settings for text size.
Styled Component definition in styled.ts
:
export const Container = styled.div`
padding: 0 2rem;
width: 100%;
max-width: 120remrem;
margin: 0 auto;
`
Usage:
export const DashboardPage: NextPage = () => (
<LayoutInternal>
<Container>
...
</Container>
</LayoutInternal>
)
Use media queries to achieve responsive layouts. Alternatives to use instead of mqs like clamp()
or grid
s.
Media query definition inside mq.ts
:
export const ScreenSize = {
medium: 768,
large: 1200,
}
export const mq = {
smallOnly: `@media (max-width: ${ScreenSize.medium / 16}em)`,
medium: `@media (min-width: ${ScreenSize.medium / 16}em)`,
large: `@media (min-width: ${ScreenSize.large / 16}em)`,
}
Usage inside Container
:
export const Container = styled.div`
margin: 0 auto;
padding: 0 0.8rem;
max-width: ${ScreenSize.large / 10}rem;
box-sizing: content-box;
${mq.medium} {
padding: 0 2rem;
}
${mq.large} {
padding: 0 4rem;
}
`
Passing props
to a styled component to modify its CSS.
type ButtonProps = {
size?: 'small' | 'medium'
accent?: 'normal' | 'primary' | 'destructive'
}
export const Button = styled.button<ButtonProps>`
--text-color: ${colors.text.inverted};
--background-color: ${colors.background.dark};
--background-color-hover: ${colors.background.dark};
${StyleReset}
${typography.label.large}
padding: 0.8em 5.4em;
color: var(--text-color);
border-radius: 4px;
transition: background-color 0.3s;
background-color: var(--background-color);
&:disabled {
--text-color: ${colors.text.inactive};
--background-color: ${colors.background.inactive};
}
&:not(:disabled) {
cursor: pointer;
&:hover,
&:focus {
background-color: var(--background-color-hover);
}
}
${(props) =>
props.accent === 'primary' &&
css`
--background-color: ${colors.accent.primary};
--background-color-hover: ${colors.accent.primaryHover};
`}
${(props) =>
props.accent === 'destructive' &&
css`
--background-color: ${colors.accent.destructive};
--background-color-hover: ${colors.accent.destructiveHover};
`}
${(props) =>
props.size === 'small' &&
css`
${typography.label.medium}
padding: 0.3em 2em 0.2em;
`}
`
export const LoginPage: NextPage = () => (
<LayoutExternal>
<FormWrapper>
...
<Button
type="submit"
size="small"
accent="primary"
>
Login
</Button>
...
</FormWrapper>
</LayoutExternal>
)
export const H1 = styled.h1`
${AccessibleHidden}
`
export const H2 = styled(H1).attrs({ as: 'h2' })``
export const SubmitButton = styled(Button).attrs({
type: 'submit',
accent: 'primary',
})``
Additional styles can by passed to attrs
between backticks.
export const Description = styled.p`
margin: 3rem 0;
`
export const Article = styled.article`
[...]
${Description} {
margin: 0;
}
`
export const AccessibleHidden = css`
opacity: 0.001;
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
`
export const H1 = styled.h1`
${AccessibleHidden}
`
Partial styles can be responsive:
const allHeadingsStyle = css`
font-family: ${font.headings};
font-weight: inherit;
`
export const typography = {
heading: {
h1: css`
${allHeadingsStyle}
font-size: 3rem;
${mq.medium} {
font-size: 4.5rem;
}
`,
...
}
}
Animations can be re-triggered by changing key
prop on the component.
import styled, { keyframes } from 'styled-components'
const shake = keyframes`
from { transform: none; }
20% { transform: translateX(-1.5rem); }
40% { transform: translateX(1.5rem); }
60% { transform: translateX(-1.5rem); }
80% { transform: translateX(1.5rem); }
to { transform: none; }
`
export const Label = styled.label`
animation: 0.5s ${shake};
`
Libraries for animations that work well with React: Framer Motion, react-transition-group, Swiper.