Overview
The styled function is the core API for creating styled components in styled-static. It provides a familiar template literal syntax that extracts CSS to static files at build time.
Basic Usage
Style HTML elements directly using styled.element syntax:
import { styled } from '@alex.radulescu/styled-static';
const Button = styled.button`
padding: 0.5rem 1rem;
background: #3b82f6;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #2563eb;
}
`;
// Usage
<Button onClick={handleClick}>Click me</Button>
All CSS is extracted at build time. The component becomes a lightweight runtime wrapper that merges class names.
Styling Different Elements
Every HTML element is available through the styled object:
const Heading = styled.h1`
font-size: 2.5rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -0.02em;
`;
const Paragraph = styled.p`
margin: 0 0 1rem;
color: var(--color-text);
line-height: 1.6;
`;
const Input = styled.input`
padding: 0.5rem;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-family: inherit;
&:focus {
outline: none;
border-color: #3b82f6;
}
`;
CSS Nesting
styled-static uses native CSS nesting for pseudo-classes, pseudo-elements, and child selectors:
const Card = styled.div`
padding: 1rem;
background: white;
border-radius: 8px;
/* Pseudo-classes */
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* Child selectors */
& h2 {
margin: 0 0 0.5rem;
}
/* Pseudo-elements */
&::before {
content: '';
position: absolute;
}
/* Media queries */
@media (max-width: 640px) {
padding: 0.5rem;
}
`;
Native CSS nesting is supported in all modern browsers (Chrome 112+, Safari 16.5+, Firefox 117+). No build-time transformation needed.
Type Safety
Styled components automatically infer props from the underlying HTML element:
const Button = styled.button`
padding: 0.5rem 1rem;
background: blue;
`;
// ✅ Type-safe: button props are available
<Button type="submit" disabled>Submit</Button>
const Link = styled.a`
color: blue;
text-decoration: none;
`;
// ✅ Type-safe: anchor props are available
<Link href="/path" target="_blank">External</Link>
Adding Custom Classes
You can pass additional classes via the className prop. User classes override styled classes:
const Button = styled.button`
padding: 0.5rem 1rem;
background: blue;
`;
// Merge with custom classes
<Button className="mt-4 hover:opacity-80">Click me</Button>
// Renders: class="ss-abc123 mt-4 hover:opacity-80"
The cascade order is: Base styles → Extension styles → User className. This ensures user classes always have the final say.
Complete Example
Here’s a real-world example from the styled-static documentation site:
import { styled } from '@alex.radulescu/styled-static';
const StyledButton = styled.button`
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-family: inherit;
background: var(--color-primary);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
&:hover {
background: var(--color-primary-hover);
}
&:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
const Counter = styled.div`
display: flex;
align-items: center;
gap: 1rem;
font-size: 1.5rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
`;
function App() {
const [count, setCount] = useState(0);
return (
<Counter>
<StyledButton onClick={() => setCount(count - 1)}>-</StyledButton>
<span>{count}</span>
<StyledButton onClick={() => setCount(count + 1)}>+</StyledButton>
</Counter>
);
}
Build Output
At build time, styled components are transformed into lightweight wrappers:
// What you write:
const Button = styled.button`
padding: 1rem;
background: blue;
`;
// What gets generated:
import { createElement } from 'react';
import { m } from '@alex.radulescu/styled-static/runtime';
import '@alex.radulescu/styled-static:abc123-0.css';
const Button = Object.assign(
(p) => createElement('button', {...p, className: m('ss-abc123', p.className)}),
{ className: 'ss-abc123' }
);
The CSS is extracted to a virtual module:
/* Virtual module: styled-static:abc123-0.css */
.ss-abc123 {
padding: 1rem;
background: blue;
}
The runtime m function is just 45 bytes and only handles className merging. Everything else happens at build time.