Selectors
Selectors are a powerful feature in PikaCSS that allows you to define custom selectors and transform them into complex CSS selectors. This enables you to create more expressive styles and simplify common CSS patterns.
How Selectors Work in PikaCSS
In PikaCSS, selectors can be defined in your configuration file and used in your styles. PikaCSS supports two types of selectors:
- Static selectors: Direct mapping to specific CSS selectors
- Dynamic selectors: Using RegExp matching patterns and transform functions
The selector system works by transforming custom selectors you use in your styles into actual CSS selectors, all done at build time with no runtime overhead.
Defining Custom Selectors
In your pika.config.ts
file, you can define custom selectors:
import { defineEngineConfig } from '@pikacss/vite-plugin-pikacss'
export default defineEngineConfig({
selectors: {
selectors: [
// Static selectors - [name, value]
[':hover', '$:hover'],
[':focus', '$:focus'],
[':active', '$:active'],
// Media query selectors
['@light', 'html:not(.dark) $'],
['@dark', 'html.dark $'],
// Responsive breakpoints
['@screen-sm', '@media screen and (min-width: 576px) and (max-width: 767.9px)'],
['@screen-md', '@media screen and (min-width: 768px) and (max-width: 991.9px)'],
['@screen-lg', '@media screen and (min-width: 992px) and (max-width: 1199.9px)'],
]
}
})
Selector Configuration Options
Selector configurations can take various forms:
1. Static Selectors
Using array syntax [selector name, CSS selector]
:
selectors: [
// Static selectors
[':hover', '$:hover'],
[':focus', '$:focus'],
['@dark', 'html.dark $'],
]
Here, the :hover
selector will be transformed to $:hover
(which ultimately becomes .xxx:hover
).
2. Dynamic Selectors
Using RegExp and transform functions:
selectors: [
// Dynamic selector
[/^@screen-(\d+)$/, m => `@media (min-width: ${m[1]}px)`, ['@screen-768', '@screen-1024']],
]
Dynamic selectors allow you to create more flexible selector patterns that can generate different outputs based on parameters in the name.
3. Object Syntax
You can also use object syntax to define selectors:
selectors: [
{
selector: '@dark',
value: 'html.dark $',
},
{
selector: /^@screen-(\d+)$/,
value: m => `@media (min-width: ${m[1]}px)`,
autocomplete: ['@screen-768', '@screen-1024'],
},
]
Using Custom Selectors in Styles
Once you have defined custom selectors, you can use them in your styles:
// Using pseudo-class selectors
pika({
'color': 'black',
':hover': {
color: 'blue',
},
})
// Using theme selectors
pika({
'color': 'black',
'@dark': {
color: 'white',
},
})
// Using responsive breakpoints
pika({
'fontSize': '1rem',
'@screen-md': {
fontSize: '1.25rem',
},
'@screen-lg': {
fontSize: '1.5rem',
},
})
Selector Nesting
PikaCSS supports multi-level nested syntax, allowing you to freely combine various CSS features:
pika({
'display': 'flex',
'gap': '1rem',
// Using selectors within media queries
'@screen-md': {
':hover': {
transform: 'scale(1.05)',
},
},
// Using media queries within selectors
':hover': {
'@media (prefers-reduced-motion)': {
transition: 'none',
},
},
// Mixing multiple selectors and media queries
'$.active::before': {
'content': '"✓"',
'@screen-sm': {
display: 'none',
},
},
})
WARNING
Considering TypeScript's performance, we limit the nesting level to 5 levels, which should be sufficient for most requirements.
Selectors with TypeScript Support
When you define selectors in your configuration, PikaCSS automatically generates TypeScript definitions, providing autocomplete:
// TypeScript will show autocomplete for your defined selectors
pika({
'@dark': { // ✓ Valid - autocomplete works
color: 'white',
},
'@unknown-selector': { // ✗ Unknown selector - no autocomplete
color: 'blue',
},
})
Real World Use Cases
1. Theme Switching
Implementing light/dark theme switching with selectors:
// Define theme selectors in your config
selectors: [
['@light', 'html:not(.dark) $'],
['@dark', 'html.dark $'],
]
// Use in your application
const styles = {
card: pika({
'backgroundColor': 'white',
'color': 'black',
'boxShadow': '0 2px 8px rgba(0, 0, 0, 0.1)',
'@dark': {
backgroundColor: '#222',
color: 'white',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.5)',
}
})
}
// Toggle dark theme in HTML
document.documentElement.classList.toggle('dark')
2. Responsive Design
Creating responsive layouts:
const styles = {
header: pika({
'fontSize': '1.25rem',
'padding': '0.5rem',
'@screen-md': {
fontSize: '1.5rem',
padding: '1rem',
},
'@screen-lg': {
fontSize: '2rem',
padding: '1.5rem',
}
})
}
3. Complex State Management
Managing multiple component states:
// Define state selectors in your config
selectors: [
[':disabled', '$:disabled'],
[':focus', '$:focus'],
[':hover', '$:hover'],
[':active', '$:active'],
['.loading', '$.loading'],
['.error', '$.error'],
]
// Apply in your component
const styles = {
button: pika({
'backgroundColor': 'blue',
'color': 'white',
':hover': {
backgroundColor: 'darkblue',
},
':active': {
transform: 'scale(0.98)',
},
':disabled': {
backgroundColor: 'gray',
cursor: 'not-allowed',
},
'.loading': {
opacity: '0.7',
},
'.error': {
backgroundColor: 'red',
}
})
}
Best Practices
Naming Consistency: Use consistent naming patterns for your selectors, such as using the
@
prefix for media queries or special selectors.Logical Organization: Organize your selectors in a logical order, such as listing base styles first, then state variations (hover, focus, etc.), and finally responsive rules.
Provide Autocomplete: Always provide
autocomplete
options for dynamic selectors to enhance the developer experience.Reuse Common Patterns: Create custom selectors for commonly used selector patterns to reduce repetitive code.
Avoid Excessive Nesting: While PikaCSS supports deep nesting, excessive nesting can make your code harder to maintain and understand.
Common Selector Examples
Here are some common selector configuration examples:
import { defineEngineConfig } from '@pikacss/vite-plugin-pikacss'
export default defineEngineConfig({
selectors: {
selectors: [
// Theme related
['@light', 'html:not(.dark) $'],
['@dark', 'html.dark $'],
// Responsive breakpoints
['@screen-xs', '@media screen and (max-width: 575.9px)'],
['@screen-sm', '@media screen and (min-width: 576px) and (max-width: 767.9px)'],
['@screen-md', '@media screen and (min-width: 768px) and (max-width: 991.9px)'],
['@screen-lg', '@media screen and (min-width: 992px) and (max-width: 1199.9px)'],
['@screen-xl', '@media screen and (min-width: 1200px) and (max-width: 1399.9px)'],
['@screen-xxl', '@media screen and (min-width: 1400px)'],
// Dynamic responsive breakpoints
[/^@min-w-(\d+)$/, m => `@media (min-width: ${m[1]}px)`, ['@min-w-640', '@min-w-768', '@min-w-1024']],
// Common interaction states
[':hover', '$:hover'],
[':focus', '$:focus'],
[':active', '$:active'],
[':disabled', '$:disabled'],
// Dark mode interaction states
['@dark:hover', 'html.dark $:hover'],
// Print media query
['@print', '@media print'],
// Other media queries
['@motion-reduce', '@media (prefers-reduced-motion: reduce)'],
['@portrait', '@media (orientation: portrait)'],
['@landscape', '@media (orientation: landscape)'],
]
}
})