CSS-in-JS 🎨
V této části se podíváme na pokročilé CSS-in-JS techniky, které umožňují psát CSS přímo v JavaScriptu s plnou podporou dynamických stylů.
🎯 Co je CSS-in-JS?
CSS-in-JS je přístup, který umožňuje psát CSS styly přímo v JavaScriptu. Styly jsou generovány za běhu a automaticky vkládány k příslušným komponentám.
Jde vlastně o podobný přístup jako inline styly, ale nabízejí více možností.
Výhody:
✅ Zápis stylů v JavaScriptu - místo CSS souborů. ✅ Podmíněné (dynamické) styly - styly se mění podle props a state - tj. např. na základě stavu komponenty. ✅ TypeScript podpora - typování stylů. ✅ Automatické scoping - žádné konflikty názvů. ✅ Dead code elimination - nepoužité styly se odstraní. ✅ Theme support - globální témata a proměnné.
Nevýhody:
❌ Runtime overhead - styly jsou generovány za běhu, což může způsobit nižší výkon aplikace. ❌ Složitější setup - většinou potřebujeme instalovat další balíčky. ❌ Menší komunita - jelikož nejde o příliš rozšířený přístup, tak je menší komunita, která vám pomůže s řešením případných problémů. ❌ Kompatibilita s novějšími CSS vlastnostmi - nemusí být podporovány všechny CSS vlastnosti, které v normálním CSS souboru můžeme použít.
🚀 styled-components
styled-components je nejpopulárnější knihovna pro CSS-in-JS.
Nabízí mnoho funkcí a vlastností, které vám pomohou psát CSS styly přímo v JavaScriptu.
Pokročilé funkce:
// EmployeeCard.jsx
import React from 'react';
import styled, { css, keyframes } from 'styled-components';
// Animace
const fadeIn = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
// Globální styly
const GlobalStyle = styled.div`
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
`;
// Téma
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
warning: '#ffc107',
info: '#17a2b8',
light: '#f8f9fa',
dark: '#343a40'
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px'
},
borderRadius: {
sm: '4px',
md: '8px',
lg: '12px',
full: '50%'
}
};
// Styled komponenty s tématem
const Card = styled.div`
background: ${props => props.theme.colors.light};
border-radius: ${props => props.theme.borderRadius.md};
padding: ${props => props.theme.spacing.md};
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
margin-bottom: ${props => props.theme.spacing.md};
animation: ${fadeIn} 0.3s ease-out;
${props => props.isSelected && css`
border: 2px solid ${props.theme.colors.primary};
background: ${props.theme.colors.light};
`}
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
${props => props.variant === 'compact' && css`
padding: ${props.theme.spacing.sm};
margin-bottom: ${props.theme.spacing.sm};
`}
`;
const Avatar = styled.div`
width: 48px;
height: 48px;
border-radius: ${props => props.theme.borderRadius.full};
background: ${props => {
const colors = props.theme.colors;
switch(props.department) {
case 'IT': return colors.primary;
case 'Design': return colors.success;
case 'Marketing': return colors.warning;
case 'HR': return colors.info;
default: return colors.secondary;
}
}};
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 18px;
margin-right: ${props => props.theme.spacing.md};
position: relative;
&::after {
content: '';
position: absolute;
top: -2px;
right: -2px;
width: 12px;
height: 12px;
border-radius: 50%;
background: ${props => props.isOnline ? props.theme.colors.success : props.theme.colors.danger};
border: 2px solid white;
}
`;
const Info = styled.div`
flex: 1;
`;
const Name = styled.h3`
margin: 0 0 4px 0;
font-size: 16px;
font-weight: 600;
color: ${props => props.theme.colors.dark};
`;
const Position = styled.p`
margin: 0 0 8px 0;
color: ${props => props.theme.colors.secondary};
font-size: 14px;
`;
const Department = styled.span`
background: ${props => props.theme.colors.light};
color: ${props => props.theme.colors.dark};
padding: 2px 8px;
border-radius: ${props => props.theme.borderRadius.lg};
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
`;
// HOC pro přidání tématu
const withTheme = (Component) => (props) => (
<ThemeProvider theme={theme}>
<Component {...props} />
</ThemeProvider>
);
const EmployeeCard = ({ employee, onClick, isSelected, variant }) => {
return (
<Card
onClick={onClick}
isSelected={isSelected}
variant={variant}
>
<Avatar
department={employee.department}
isOnline={employee.isOnline}
>
{employee.name.charAt(0)}
</Avatar>
<Info>
<Name>{employee.name}</Name>
<Position>{employee.position}</Position>
<Department>{employee.department}</Department>
</Info>
</Card>
);
};
export default withTheme(EmployeeCard);
🎨 Emotion
Instalace:
npm install @emotion/react @emotion/styled
Základní použití:
// EmployeeCard.jsx
import React from 'react';
import styled from '@emotion/styled';
import { css } from '@emotion/react';
const Card = styled.div`
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
margin-bottom: 12px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
`;
const Avatar = styled.div`
width: 48px;
height: 48px;
border-radius: 50%;
background: ${props => props.department === 'IT' ? '#007bff' : '#28a745'};
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 18px;
margin-right: 12px;
`;
const EmployeeCard = ({ employee, onClick }) => {
return (
<Card onClick={onClick}>
<Avatar department={employee.department}>
{employee.name.charAt(0)}
</Avatar>
<div css={css`
flex: 1;
`}>
<h3 css={css`
margin: 0 0 4px 0;
font-size: 16px;
font-weight: 600;
color: #333;
`}>
{employee.name}
</h3>
<p css={css`
margin: 0 0 8px 0;
color: #666;
font-size: 14px;
`}>
{employee.position}
</p>
<span css={css`
background: #e9ecef;
color: #495057;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
`}>
{employee.department}
</span>
</div>
</Card>
);
};
export default EmployeeCard;
🎨 Stitches
Instalace:
npm install @stitches/react
Použití:
// EmployeeCard.jsx
import React from 'react';
import { styled } from '@stitches/react';
const Card = styled('div', {
background: 'white',
borderRadius: '8px',
padding: '16px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
cursor: 'pointer',
transition: 'all 0.2s ease',
display: 'flex',
alignItems: 'center',
marginBottom: '12px',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.15)',
},
variants: {
variant: {
default: {
background: 'white',
},
compact: {
padding: '8px',
marginBottom: '8px',
},
},
isSelected: {
true: {
border: '2px solid #007bff',
background: '#f8f9ff',
},
},
},
});
const Avatar = styled('div', {
width: '48px',
height: '48px',
borderRadius: '50%',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
fontSize: '18px',
marginRight: '12px',
variants: {
department: {
IT: {
background: '#007bff',
},
Design: {
background: '#28a745',
},
Marketing: {
background: '#ffc107',
},
},
},
});
const EmployeeCard = ({ employee, onClick, variant, isSelected }) => {
return (
<Card
onClick={onClick}
variant={variant}
isSelected={isSelected}
>
<Avatar department={employee.department}>
{employee.name.charAt(0)}
</Avatar>
<div style={{ flex: 1 }}>
<h3 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: '600' }}>
{employee.name}
</h3>
<p style={{ margin: '0 0 8px 0', color: '#666', fontSize: '14px' }}>
{employee.position}
</p>
<span style={{
background: '#e9ecef',
color: '#495057',
padding: '2px 8px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: '500'
}}>
{employee.department}
</span>
</div>
</Card>
);
};
export default EmployeeCard;
🎨 linaria
linaria je knihovna pro CSS-in-JS se snahem o co nejnižší dopad na runtime overhead.
Proto se při buildu aplikace styly automaticky extrahují do CSS souboru, aby se většina stylování nemusela řešit za běhu aplikace.
Instalace:
npm install linaria
Použití:
// EmployeeCard.jsx
import React from 'react';
import { styled } from 'linaria/react';
const Card = styled.div`
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
margin-bottom: 12px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
},
});
const Avatar = styled.div`
width: 48px;
height: 48px;
border-radius: 50%;
background: ${props => props.department === 'IT' ? '#007bff' : '#28a745'};
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 18px;
margin-right: 12px;
};
const EmployeeCard = ({ employee, onClick }) => {
return (
<Card onClick={onClick}>
<Avatar department={employee.department}>
{employee.name.charAt(0)}
</Avatar>
<div>
</div>
</Card>
);
};
export default EmployeeCard;
🔄 Porovnání CSS-in-JS knihoven
| Knihovna | Výhody | Nevýhody |
|---|---|---|
| styled-components | ✅ Nejpopulárnější, bohatá ekosystém | ❌ Runtime overhead |
| Emotion | ✅ Výkonnější, menší bundle | ❌ Méně funkcí |
| Stitches | ✅ Zero-runtime, TypeScript | ❌ Novější, menší komunita |
| linaria | ✅ Zero-runtime, type-safe | ❌ Složitější setup |
📋 Praktické cvičení
- Implementujte EmployeeCard s styled-components
- Přidejte téma s globálními proměnnými
- Vytvořte animace pro hover efekty
- Přidejte varianty pro různé styly karet
- Porovnejte výkon s CSS moduly
🚀 Tipy a triky
Optimalizace výkonu:
// Použijte React.memo pro styled komponenty
const MemoizedCard = React.memo(styled.div`
/* styly */
`);
// Definujte styly mimo komponentu
const cardStyles = css`
background: white;
border-radius: 8px;
`;
TypeScript podpora:
interface CardProps {
variant: 'default' | 'compact';
isSelected?: boolean;
}
const Card = styled.div<CardProps>`
/* styly s typováním */
`;
V příští části se podíváme na TailwindCSS! 🎨