CSS For JavaScript Module 3 Notes
Modern Component Architecture
One of the largest concerns/problems with CSS is that it has a global nature. Large projects may have multiple things which affect one element. So how can we know which styles apply directly to a certain element?
We can use styled-components alongside of React to cover CSS fundamentals but in a modern context.
// ...
return (
<FormWrapper>
<IntroParagraph>This is a paragraph</IntroParagraph>
</FormWrapper>
const FormWrapper = styled.form`
border: 1px solid;
font-weight: bold;
`;
const IntroParagraph = styled.p`
color: red;
`;
)
Styled Components 101
This library uses an obscure JS featured named tagged template literals. styled.form is a function, which gets passed a template string as an argument. Styled-components also uses a Sass-like preprocessor behind the scenes which is called stylis. This adds vendor prefixes for maximum browser comparability. It also allows us to user & to reference the soon-to-be-created class:
const Button = styled.button`
display: flex,
&:hover {
color: red;
}
`;
Component names don't have to be globally unique. The & character can be thought of as a placeholder. It gets replaced by a class name, once that class has been generated. It allows us to use additional selectors. The styled object has methods for every HTML tag. Examples include styled.div, styled.a, styled.canvas etc.
CSS Prop
An alternative writing method for styled-components is the css prop.
const Title = ({ id, children }) => {
return (
<h1
id={id}
css={`
font-size: 2rem;
font-weight: bold;
`}
>
{children}
</h1>
)
}
- Has a similar appearance to inline css, but it's actually full-css
- This feels messy and bloats jsx
Global Styles
With styled-components, the styles are bound to the elements we create. But what about global styles? Styled-components has an API for creating global styles:
import { createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
*, *::before, *::after {
box-sizing: border-box;
}
html {
font-size: 1.125rem;
}
body {
background-color: hsl(0deg 0% 95%);
}
`
export default GlobalStyles
This will return a component GlobalStyles which we can render in our app to utilise global styles. Our App.js file might look something like this now.
import GlobalStyles from '../GlobalStyles'
function App() {
return (
<Wrapper>
<Router>{/* An entire app here! */}</Router>
<GlobalStyles />
</Wrapper>
)
}
export default App
Now our styles from GlobalStyles will be applied through out our entire app.
Dynamic Styles
Styled-components allow us to dynamically alter our styles dependant on the applications state. We can utilise styles in React in a few different ways.
Inline Styles Quick and easy to add. However they make it harder to understand what's going on by "splitting up" where the CSS definitions live. They also aren't compatible with media queries, pseudo-classes, and any other CSS which is not a straight-up property or value. Note: When setting properties in JS, we use camelCase versions of CSS property names.
Interpolation Functions The official styled-components way of managing dynamic styles is to use this. The API leverages tagged template literals.
const Button == ({ color, onClick, children }) => {
return (
<Wrapper onClick={onClick} color={color}>
{children}
</Wrapper?>
);
}
const Wrapper = styled.button`
color: ${props => props.color};
padding: 16px 24px;
`;
CSS Variables This method offers a greater developer experience. It's quick and easy to add new styles, and feels lower-friction than using styled-components interpolations. They are however, a bit less flexible. They can only be used to provide property values (not ideal for media queries).
const Button = ({ color, onClick, children }) => {
return (
<Wrapper onClick={onClick} style={{ '--color': color }}>
{children}
</Wrapper>
)
}
const Wrapper = styled.button`
color: var(--color);
padding: 16px 24px;
`
Component Libraries
This is a collection of generic, reusable components which can be dropped into multiple different applications. It ensures consistency between products and can help speed up development. Angular's material component library is an example of one.
Design Systems and Design Tokens
A design system is essentially a 'designer version' of a component library. It is made up of a suite of vector graphics produced in tools like Figma, Sketch or Adobe Illustrator. Design systems also consist of design tokens. A lot of systems will specify scales for sizes or colors, and a design token is a value in that scale. or example, rather than allow all possible font size, we limit it to a subset (0.75rem, 0.875rem, 1 rem, 1.25rem 1.5rem etc).
Composition
Say we need multiple variants of a single component, we can choose one component to serve as the base for another. See:
import styled from 'styled-components'
export default function App() {
return <PrimaryButton>Button</PrimaryButton>
}
const Base = styled.button`
font-size: 21px;
`
const PrimaryButton = styled(Base)`
background: blue;
color: white;
`
The styles of both Base and PrimaryButton will be applied to the button.
Dynamic Tags
In styled components, we can use the as tag to dynamically change the tag that styled components render. For example, we want to render a button, that when clicked acts as an a tag and links to a new page.
function Button({ href, children }) {
return (
<Wrapper href={href} as={href ? 'a' : 'button'}>
{children}
</Wrapper>
)
}
const Wrapper = styled.button`
// ...
`
const App = () => <Button href="/">Hello</Button>
export default App
Escape Hatches
Sometimes our components are not 100% suited to the situation at hand. For example, we have a suite of different styled button components that are all blue, but for this use case we need a red button for emphasis. We could update our original component to accept a 'mood' prop (danger, calm, default etc).
For one-off use cases, we could implement the changes with composition.
import Button from './Button' // Our initial component
const SpookyButton = styled(Button)`
font-family: 'Spooky Halloween Font';
background-color: orange;
color: black;
`
export default SpookyButton
Because we are composing another component rather than just a styled-component, we need to make a minor change. In our initial component, we must add a className prop to our component and pass it along to the rendered 'Component'.
By forwarding className, we have given ourselves an escape hatch. These are tools which allow us to break free of the constraints that normally serve us well.
Single Source of Styles
Using styled components, 'contextual styles' can become an issue. For example, rendering a text-link should look like regular text when within a block-quote, but blue outside of the block-quote. How to fix this:
const TextLink = styled.a`
// ...
${QuoteContent} & {
color: black;
text-decoration: revert;
}
`
With this approach we avoid reaching in and setting TextLink styles from another component. We also don't have to rely on our own memory.