Styling in @meonode/ui
is designed to be type-safe, theme-aware, and highly composable by leveraging a powerful CSS-in-JS engine. The library simplifies styling by allowing you to use CSS properties directly as props on your components, while providing an advanced css
prop for more complex scenarios.
css
Prop and Direct Prop Styling@meonode/ui
intelligently differentiates between standard CSS properties and DOM attributes. You can apply most CSS properties directly to a component as props, and for advanced features, you can use the dedicated css
prop.
For simple, one-off styles, use CSS properties directly as component props:
Button('Click Me', { backgroundColor: 'tomato', padding: '12px 24px', borderRadius: 8, color: 'white', cursor: 'pointer' })
css
PropThe css
prop accepts a JavaScript object and is the primary method for defining complex styles like nested selectors, pseudo-classes, and media queries:
Div({ padding: '20px', css: { '&:hover': { transform: 'scale(1.05)' }, '@media (max-width: 768px)': { padding: '12px' } } })
The css
prop is powered by @emotion/react
under the hood, enabling all of the library's advanced styling features.
css
PropCreate engaging interactive experiences using pseudo-classes:
import { Button, Card, Input } from '@meonode/ui'; const InteractiveButton = Button('Hover & Click Me', { padding: '14px 28px', backgroundColor: '#3B82F6', color: 'white', borderRadius: '10px', border: 'none', cursor: 'pointer', fontSize: '16px', fontWeight: '600', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', css: { // Hover state '&:hover': { backgroundColor: '#2563EB', transform: 'translateY(-3px)', boxShadow: '0 10px 20px rgba(59, 130, 246, 0.4)' }, // Active state (when clicked) '&:active': { transform: 'translateY(-1px)', boxShadow: '0 5px 10px rgba(59, 130, 246, 0.3)', transition: 'all 0.1s ease' }, // Focus state (accessibility) '&:focus': { outline: 'none', boxShadow: '0 0 0 4px rgba(59, 130, 246, 0.25)' }, // Focus visible (keyboard navigation) '&:focus-visible': { outline: '2px solid #1D4ED8', outlineOffset: '2px' }, // Disabled state '&:disabled': { backgroundColor: '#9CA3AF', cursor: 'not-allowed', transform: 'none', boxShadow: 'none' }, // First/last child selectors '&:first-of-type': { marginLeft: 0 }, '&:last-of-type': { marginRight: 0 } } }); const InteractiveCard = Card({ padding: '24px', backgroundColor: 'white', borderRadius: '16px', border: '1px solid #E5E7EB', cursor: 'pointer', css: { // Hover effects '&:hover': { borderColor: '#3B82F6', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', transform: 'translateY(-2px)' }, // Focus state for card navigation '&:focus': { outline: 'none', borderColor: '#2563EB', boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)' }, // Visited state (if card is a link) '&:visited': { borderColor: '#7C3AED' } } }); const SmartInput = Input({ padding: '12px 16px', borderRadius: '8px', border: '2px solid #D1D5DB', fontSize: '16px', css: { // Focus state '&:focus': { outline: 'none', borderColor: '#3B82F6', boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)' }, // Invalid state '&:invalid': { borderColor: '#EF4444', boxShadow: '0 0 0 3px rgba(239, 68, 68, 0.1)' }, // Valid state '&:valid': { borderColor: '#10B981' }, // Placeholder styling '&::placeholder': { color: '#9CA3AF', fontSize: '14px' }, // Required field indicator '&:required': { position: 'relative' }, '&:required::after': { content: '"*"', color: '#EF4444', position: 'absolute', right: '8px', top: '50%', transform: 'translateY(-50%)' } } });
Use pseudo-elements to create decorative effects and enhanced layouts:
import { Div, H2, Button, Text } from '@meonode/ui'; const DecoratedCard = Div({ position: 'relative', padding: '32px', backgroundColor: 'white', borderRadius: '16px', overflow: 'hidden', css: { // Top decorative border using ::before '&::before': { content: '""', position: 'absolute', top: 0, left: 0, right: 0, height: '4px', background: 'linear-gradient(90deg, #FF6B6B, #4ECDC4, #45B7D1, #96CEB4)', borderRadius: '16px 16px 0 0' }, // Floating badge using ::after '&::after': { content: '"NEW"', position: 'absolute', top: '16px', right: '16px', backgroundColor: '#EF4444', color: 'white', padding: '4px 8px', borderRadius: '12px', fontSize: '10px', fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: '0.5px' }, // Hover effect that affects pseudo-elements '&:hover::before': { height: '6px', transition: 'height 0.3s ease' } }, children: [ H2('Enhanced Card with Pseudo-Elements'), Text('This card uses ::before for the top border and ::after for the badge.') ] }); const QuoteBlock = Div({ position: 'relative', padding: '24px 48px', backgroundColor: '#F8FAFC', borderLeft: '4px solid #3B82F6', fontStyle: 'italic', css: { // Opening quote mark '&::before': { content: '"\\201C"', // Left double quotation mark position: 'absolute', top: '8px', left: '16px', fontSize: '48px', color: '#3B82F6', lineHeight: 1, opacity: 0.5 }, // Closing quote mark '&::after': { content: '"\\201D"', // Right double quotation mark position: 'absolute', bottom: '8px', right: '16px', fontSize: '48px', color: '#3B82F6', lineHeight: 1, opacity: 0.5 } }, children: Text('The best way to predict the future is to invent it. - Alan Kay') }); const GlowingButton = Button('Glowing Effect', { padding: '12px 24px', backgroundColor: '#6366F1', color: 'white', borderRadius: '8px', border: 'none', cursor: 'pointer', position: 'relative', css: { // Glowing effect using ::before '&::before': { content: '""', position: 'absolute', inset: 0, borderRadius: '8px', background: 'linear-gradient(45deg, #6366F1, #8B5CF6)', filter: 'blur(8px)', opacity: 0, zIndex: -1, transition: 'opacity 0.3s ease' }, '&:hover::before': { opacity: 0.7 } } });
Create sophisticated responsive layouts using various types of media queries:
import { Column, Row, Div, H1, Text, Button } from '@meonode/ui'; const ResponsiveHero = Column({ padding: '40px 20px', textAlign: 'center', backgroundColor: '#1F2937', color: 'white', minHeight: '60vh', display: 'flex', justifyContent: 'center', alignItems: 'center', css: { // Mobile First Approach fontSize: '16px', // Small tablets '@media (min-width: 640px)': { padding: '60px 40px', fontSize: '18px' }, // Large tablets '@media (min-width: 768px)': { padding: '80px 60px', fontSize: '20px' }, // Small desktops '@media (min-width: 1024px)': { padding: '100px 80px', fontSize: '22px', minHeight: '80vh' }, // Large desktops '@media (min-width: 1280px)': { padding: '120px 100px', fontSize: '24px' }, // Ultra-wide screens '@media (min-width: 1536px)': { maxWidth: '1400px', margin: '0 auto' }, // Orientation-based styles '@media (orientation: landscape)': { flexDirection: 'row', textAlign: 'left' }, '@media (orientation: portrait)': { flexDirection: 'column' }, // High DPI displays '@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)': { '& img': { imageRendering: 'crisp-edges' } }, // Dark mode preference '@media (prefers-color-scheme: dark)': { backgroundColor: '#0F172A', color: '#F1F5F9' }, // Light mode preference '@media (prefers-color-scheme: light)': { backgroundColor: '#F8FAFC', color: '#1E293B' }, // Reduced motion preference (accessibility) '@media (prefers-reduced-motion: reduce)': { '& *': { animation: 'none !important', transition: 'none !important' } }, // Print styles '@media print': { backgroundColor: 'white !important', color: 'black !important', boxShadow: 'none', '& button': { display: 'none' } }, // Container queries (future-proof) '@container (min-width: 500px)': { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '32px' } }, children: [ H1('Responsive Design Demo', { css: { fontSize: '2.5rem', marginBottom: '16px', '@media (max-width: 640px)': { fontSize: '2rem' }, '@media (min-width: 1024px)': { fontSize: '3.5rem' } } }), Text('This hero section adapts to different screen sizes and user preferences.') ] });
Create engaging animations using CSS keyframes:
import { Div, Button, H2, Text } from '@meonode/ui'; const AnimatedShowcase = Column({ padding: '40px', gap: '32px', backgroundColor: '#F8FAFC', borderRadius: '20px', children: [ // Fade in animation Div({ padding: '24px', backgroundColor: 'white', borderRadius: '12px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)', css: { '@keyframes fadeInUp': { '0%': { opacity: 0, transform: 'translateY(40px)' }, '100%': { opacity: 1, transform: 'translateY(0)' } }, animation: 'fadeInUp 0.8s ease-out' }, children: [ H2('Fade In Animation'), Text('This card fades in from bottom when loaded.') ] }), // Pulse animation Button('Pulsing Button', { padding: '16px 32px', backgroundColor: '#10B981', color: 'white', borderRadius: '12px', border: 'none', cursor: 'pointer', fontSize: '18px', fontWeight: 'bold', css: { '@keyframes pulse': { '0%, 100%': { transform: 'scale(1)', opacity: 1 }, '50%': { transform: 'scale(1.05)', opacity: 0.8 } }, animation: 'pulse 2s infinite ease-in-out', '&:hover': { animation: 'none', transform: 'scale(1.1)', boxShadow: '0 8px 25px rgba(16, 185, 129, 0.4)' } } }), // Rotating loader Div({ width: '60px', height: '60px', border: '4px solid #E5E7EB', borderTop: '4px solid #3B82F6', borderRadius: '50%', css: { '@keyframes spin': { '0%': { transform: 'rotate(0deg)' }, '100%': { transform: 'rotate(360deg)' } }, animation: 'spin 1s linear infinite' } }), // Complex multi-step animation Div({ width: '100px', height: '100px', backgroundColor: '#F59E0B', borderRadius: '50%', css: { '@keyframes complexAnimation': { '0%': { transform: 'scale(1) rotate(0deg)', borderRadius: '50%', backgroundColor: '#F59E0B' }, '25%': { transform: 'scale(1.2) rotate(90deg)', borderRadius: '25%', backgroundColor: '#EF4444' }, '50%': { transform: 'scale(1) rotate(180deg)', borderRadius: '0%', backgroundColor: '#8B5CF6' }, '75%': { transform: 'scale(0.8) rotate(270deg)', borderRadius: '25%', backgroundColor: '#10B981' }, '100%': { transform: 'scale(1) rotate(360deg)', borderRadius: '50%', backgroundColor: '#F59E0B' } }, animation: 'complexAnimation 4s ease-in-out infinite', '&:hover': { animationDuration: '1s' } } }), // Staggered animation Row({ gap: '8px', children: Array.from({ length: 5 }, (_, i) => Div({ key: i, width: '20px', height: '60px', backgroundColor: '#3B82F6', borderRadius: '4px', css: { '@keyframes wave': { '0%, 40%, 100%': { transform: 'scaleY(0.4)' }, '20%': { transform: 'scaleY(1)' } }, animation: `wave 1.2s ease-in-out infinite`, animationDelay: `${i * 0.1}s` } }) ) }) ] });
Create sophisticated styling patterns using complex selectors:
import { Column, Div, Button, H3, Text, List, ListItem } from '@meonode/ui'; const AdvancedSelectorDemo = Column({ padding: '32px', backgroundColor: '#FFFFFF', borderRadius: '16px', boxShadow: '0 10px 25px rgba(0,0,0,0.1)', css: { // Style all direct children '& > *': { marginBottom: '20px', transition: 'all 0.3s ease' }, // Remove margin from last child '& > *:last-child': { marginBottom: 0 }, // Style all buttons within this container '& button': { transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.5px' }, // Button hover states '& button:hover': { transform: 'scale(1.03) translateY(-1px)', filter: 'brightness(1.1)' }, // Style specific button variants '& button[data-variant="primary"]': { backgroundColor: '#3B82F6', color: 'white', boxShadow: '0 4px 14px rgba(59, 130, 246, 0.3)' }, '& button[data-variant="secondary"]': { backgroundColor: 'transparent', color: '#3B82F6', border: '2px solid #3B82F6' }, '& button[data-variant="danger"]': { backgroundColor: '#EF4444', color: 'white', boxShadow: '0 4px 14px rgba(239, 68, 68, 0.3)' }, // Typography cascade styling '& h3': { color: '#1F2937', fontWeight: '700', fontSize: '1.5rem', marginBottom: '12px' }, '& p': { color: '#6B7280', lineHeight: '1.6', fontSize: '16px' }, // Adjacent sibling selector '& h3 + p': { marginTop: '8px', fontSize: '18px', color: '#4B5563' }, // Child combinator for nested elements '& .card > .header': { borderBottom: '2px solid #E5E7EB', paddingBottom: '16px', marginBottom: '16px' }, // Attribute selectors '& [data-highlight="true"]': { backgroundColor: '#FEF3C7', padding: '12px', borderRadius: '8px', border: '2px solid #F59E0B', fontWeight: 'bold' }, // Nth-child patterns '& .item:nth-of-type(odd)': { backgroundColor: '#F3F4F6', transform: 'translateX(8px)' }, '& .item:nth-of-type(even)': { backgroundColor: '#FFFFFF', transform: 'translateX(-8px)' }, // First and last item special styling '& .item:first-of-type': { borderTop: '3px solid #3B82F6', fontWeight: 'bold' }, '& .item:last-of-type': { borderBottom: '3px solid #3B82F6', fontWeight: 'bold' }, // Complex selectors with multiple conditions '& .item:nth-of-type(3n):hover': { backgroundColor: '#DBEAFE', transform: 'scale(1.02)' }, // Descendant selectors '& .section .content': { padding: '16px', borderLeft: '4px solid #10B981' }, // Universal selector within context '& .special-section *': { color: '#7C3AED !important' } }, children: [ H3('Advanced Selector Showcase'), Text('This demonstrates various CSS selector capabilities.'), Row({ gap: '12px', flexWrap: 'wrap', children: [ Button('Primary', { 'data-variant': 'primary' }), Button('Secondary', { 'data-variant': 'secondary' }), Button('Danger', { 'data-variant': 'danger' }) ] }), Div({ className: 'card', padding: '16px', backgroundColor: '#F9FAFB', borderRadius: '8px', children: [ Div({ className: 'header', children: H3('Card Header') }), Div({ className: 'content', children: Text('Card content with special styling.') }) ] }), Text('This text is highlighted!', { 'data-highlight': 'true' }), // List with nth-child styling Column({ gap: '8px', children: Array.from({ length: 6 }, (_, i) => Div({ key: i, className: 'item', padding: '12px 16px', borderRadius: '6px', children: `List item ${i + 1}` }) ) }) ] });
Leverage CSS custom properties for dynamic, runtime theming:
import { Div, Button, H2, Row } from '@meonode/ui'; import { useState } from 'react'; const DynamicThemeComponent = Component(() => { const [theme, setTheme] = useState('blue'); const themes = { blue: { primary: '#3B82F6', secondary: '#1E40AF', accent: '#93C5FD' }, green: { primary: '#10B981', secondary: '#047857', accent: '#6EE7B7' }, purple: { primary: '#8B5CF6', secondary: '#6D28D9', accent: '#C4B5FD' }, orange: { primary: '#F59E0B', secondary: '#D97706', accent: '#FCD34D' } }; return Div({ padding: '32px', borderRadius: '16px', css: { // Define CSS custom properties dynamically '--primary-color': themes[theme].primary, '--secondary-color': themes[theme].secondary, '--accent-color': themes[theme].accent, '--border-radius': '8px', '--shadow-color': `${themes[theme].primary}40`, // 40 for opacity // Use the custom properties throughout backgroundColor: 'var(--primary-color)', color: 'white', boxShadow: '0 8px 25px var(--shadow-color)', // Nested elements using CSS variables '& h2': { color: 'var(--accent-color)', textShadow: '0 2px 4px rgba(0,0,0,0.3)' }, '& button': { backgroundColor: 'var(--secondary-color)', borderRadius: 'var(--border-radius)', border: 'none', color: 'white', padding: '10px 20px', cursor: 'pointer', margin: '4px', transition: 'all 0.3s ease' }, '& button:hover': { backgroundColor: 'var(--accent-color)', transform: 'translateY(-2px)', boxShadow: '0 6px 12px var(--shadow-color)' }, // Dynamic theme-based styles '&[data-theme="blue"]': { background: 'linear-gradient(135deg, var(--primary-color), var(--secondary-color))' }, '&[data-theme="green"]': { background: 'radial-gradient(circle, var(--primary-color), var(--secondary-color))' }, '&[data-theme="purple"]': { background: 'conic-gradient(var(--primary-color), var(--accent-color), var(--secondary-color))' }, '&[data-theme="orange"]': { background: 'linear-gradient(45deg, var(--primary-color), var(--accent-color))' } }, 'data-theme': theme, children: [ H2('Dynamic CSS Variables Demo'), Text('Switch themes to see CSS custom properties in action.'), Row({ gap: '8px', flexWrap: 'wrap', children: Object.keys(themes).map(themeName => Button(themeName.charAt(0).toUpperCase() + themeName.slice(1), { key: themeName, onClick: () => setTheme(themeName), css: { opacity: theme === themeName ? 1 : 0.7, transform: theme === themeName ? 'scale(1.1)' : 'scale(1)' } }) ) }) ] }); });
Create sophisticated animation sequences and interactive effects:
import { Div, Button, Text } from '@meonode/ui'; const AnimationPlayground = Column({ padding: '40px', gap: '40px', backgroundColor: '#1F2937', borderRadius: '20px', children: [ // Morphing shapes animation Div({ width: '120px', height: '120px', margin: '0 auto', css: { '@keyframes morph': { '0%': { borderRadius: '50%', backgroundColor: '#EF4444', transform: 'rotate(0deg) scale(1)' }, '25%': { borderRadius: '25%', backgroundColor: '#F59E0B', transform: 'rotate(90deg) scale(1.2)' }, '50%': { borderRadius: '0%', backgroundColor: '#10B981', transform: 'rotate(180deg) scale(1)' }, '75%': { borderRadius: '25%', backgroundColor: '#3B82F6', transform: 'rotate(270deg) scale(0.8)' }, '100%': { borderRadius: '50%', backgroundColor: '#EF4444', transform: 'rotate(360deg) scale(1)' } }, animation: 'morph 4s ease-in-out infinite' } }), // Floating elements Row({ justifyContent: 'space-around', children: Array.from({ length: 4 }, (_, i) => Div({ key: i, width: '30px', height: '30px', backgroundColor: '#8B5CF6', borderRadius: '50%', css: { '@keyframes float': { '0%, 100%': { transform: 'translateY(0px)' }, '50%': { transform: 'translateY(-20px)' } }, animation: `float 3s ease-in-out infinite`, animationDelay: `${i * 0.2}s` } }) ) }), // Ripple effect button Button('Ripple Effect', { position: 'relative', padding: '16px 32px', backgroundColor: '#6366F1', color: 'white', borderRadius: '12px', border: 'none', cursor: 'pointer', overflow: 'hidden', fontSize: '16px', fontWeight: '600', css: { '&::after': { content: '""', position: 'absolute', top: '50%', left: '50%', width: '0', height: '0', borderRadius: '50%', backgroundColor: 'rgba(255,255,255,0.5)', transform: 'translate(-50%, -50%)', transition: 'all 0.5s ease-out', opacity: 0, }, '&:active::after': { width: '200%', height: '200%', opacity: 1, transition: 'width 0.5s ease-out, height 0.5s ease-out, opacity 0.5s ease-out' }, } }) ] }); // Final rendering example const App = Column({ gap: '60px', children: [ InteractiveButton, InteractiveCard, SmartInput, DecoratedCard, QuoteBlock, GlowingButton, ResponsiveHero, AnimatedShowcase, AdvancedSelectorDemo, DynamicThemeComponent, AnimationPlayground ] });
createNode
For more advanced, theme-aware components, you can use the createNode
factory. This allows you to define a base style that can be easily extended and overridden.
import { createNode, Column, H3, Text } from '@meonode/ui'; // Define a reusable Card component with default styles const Card = createNode('div', { padding: '24px', backgroundColor: '#FFFFFF', borderRadius: '16px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', transition: 'transform 0.3s ease-in-out', css: { '&:hover': { transform: 'translateY(-5px)', boxShadow: '0 8px 24px rgba(0,0,0,0.15)', }, }, }); // Example usage const ThemedCardExample = () => { return Column({ children: [ Card({ children: [ H3('Default Card'), Text('This card inherits the base styles from Card.'), ], }), ], }).render() };
padding
, color
, and display
for quick, declarative styling.css
Prop for Complexity: Reserve the css
prop for more advanced use cases like pseudo-selectors, media queries, and animations.createNode
and Component
: For reusable design components, use createNode
to create a "factory" with default styling, or Component
for fully encapsulated, reusable React components.Q: Can I use CSS preprocessors like Sass or Less?
A: @meonode/ui
is designed to be a modern alternative to preprocessors. Its nested object syntax and built-in features (like media queries and vendor prefixes) cover most of the functionality of Sass/Less directly in JavaScript, providing a more integrated and type-safe experience.
Q: How does createNode
differ from Component
?
A: createNode
is a factory that produces a MeoNode function, which then needs to be rendered. Component
is a factory that produces a standard React component, which can be rendered directly with JSX or passed to Node()
. Use createNode
for creating new, reusable MeoNode primitives, and Component
for creating full-fledged React components that can be shared across your application.
You now have a solid grasp of @meonode/ui
's styling capabilities. To take your skills further, explore:
@meonode/ui
with frameworks like Next.js, Vite, etc.