| name | cacao-style |
| description | Style Cacao components with LESS. Use when creating component styles, working with theme variables, adding responsive design, or ensuring dark/light mode compatibility. |
Cacao Styling Guide
Cacao uses LESS for styling with CSS custom properties for theming. All styles must work in both light and dark modes.
File Structure
cacao/frontend/src/styles/
├── index.less # Main entry, imports all
├── variables.less # LESS variables (sizes, breakpoints)
├── mixins.less # Reusable LESS mixins
├── base.less # Reset, typography, utilities
├── layouts.less # App layout, grid system
├── themes/
│ ├── dark.less # Dark theme CSS variables
│ └── light.less # Light theme CSS variables
└── components/
├── display.less # Card, Metric, Table, Badge, etc.
├── form.less # Button, Input, Select, etc.
├── typography.less # Title, Text, Code, etc.
└── admin.less # AppShell, NavSidebar, etc.
Theme Variables (CSS Custom Properties)
Always use these for colors - they automatically switch with theme:
Backgrounds
var(--bg-primary)
var(--bg-secondary)
var(--bg-tertiary)
var(--bg-hover)
Text
var(--text-primary)
var(--text-secondary)
var(--text-muted)
var(--text-inverse)
Accents
var(--accent-primary)
var(--accent-hover)
var(--accent-muted)
Status Colors
var(--success)
var(--success-muted)
var(--warning)
var(--warning-muted)
var(--danger)
var(--danger-muted)
var(--info)
var(--info-muted)
Borders & Shadows
var(--border-color)
var(--shadow-sm)
var(--shadow-md)
var(--shadow-lg)
LESS Variables (Sizes & Spacing)
@font-size-xs: 0.75rem;
@font-size-sm: 0.875rem;
@font-size-base: 1rem;
@font-size-lg: 1.125rem;
@font-size-xl: 1.25rem;
@font-size-2xl: 1.5rem;
@font-size-3xl: 1.875rem;
@radius-sm: 0.25rem;
@radius-md: 0.5rem;
@radius-lg: 0.75rem;
@radius-xl: 1rem;
@radius-full: 9999px;
@spacing-1: 0.25rem;
@spacing-2: 0.5rem;
@spacing-3: 0.75rem;
@spacing-4: 1rem;
@spacing-6: 1.5rem;
@spacing-8: 2rem;
@screen-sm: 640px;
@screen-md: 768px;
@screen-lg: 1024px;
@screen-xl: 1280px;
Component Styling Pattern
Basic Component
.my-input-wrapper {
margin-bottom: @spacing-4;
}
.my-input-label {
display: block;
font-size: @font-size-sm;
color: var(--text-muted);
margin-bottom: @spacing-2;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.my-input {
width: 100%;
padding: 0.625rem 0.875rem;
border-radius: @radius-md;
border: 1px solid var(--border-color);
background: var(--bg-tertiary);
color: var(--text-primary);
font-size: @font-size-base;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
&::placeholder {
color: var(--text-muted);
}
&:hover {
border-color: var(--accent-primary);
}
&:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px var(--accent-muted);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
Size Variants
.my-button {
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 500;
border-radius: @radius-md;
transition: all 0.15s ease;
}
.my-button-sm {
padding: 0.375rem 0.75rem;
font-size: @font-size-sm;
}
.my-button-md {
padding: 0.5rem 1rem;
font-size: @font-size-base;
}
.my-button-lg {
padding: 0.625rem 1.25rem;
font-size: @font-size-lg;
}
Color Variants
.my-button {
background: var(--accent-primary);
color: var(--text-inverse);
border: none;
&:hover {
background: var(--accent-hover);
}
}
.my-button-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
&:hover {
background: var(--bg-hover);
}
}
.my-button-danger {
background: var(--danger);
color: white;
&:hover {
filter: brightness(1.1);
}
}
.my-button-ghost {
background: transparent;
color: var(--text-secondary);
&:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
}
.my-button-outline {
background: transparent;
color: var(--accent-primary);
border: 1px solid var(--accent-primary);
&:hover {
background: var(--accent-muted);
}
}
Status States
.my-alert {
padding: @spacing-4;
border-radius: @radius-md;
border-left: 4px solid;
}
.my-alert-info {
background: var(--info-muted);
border-color: var(--info);
color: var(--info);
}
.my-alert-success {
background: var(--success-muted);
border-color: var(--success);
color: var(--success);
}
.my-alert-warning {
background: var(--warning-muted);
border-color: var(--warning);
color: var(--warning);
}
.my-alert-error {
background: var(--danger-muted);
border-color: var(--danger);
color: var(--danger);
}
Common Patterns
Card Container
.my-card {
background: var(--bg-secondary);
border-radius: @radius-lg;
padding: @spacing-6;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
}
.my-card-header {
margin-bottom: @spacing-4;
padding-bottom: @spacing-4;
border-bottom: 1px solid var(--border-color);
}
.my-card-title {
font-size: @font-size-lg;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
Interactive Element
.my-interactive {
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background: var(--bg-hover);
}
&:active {
transform: scale(0.98);
}
&:focus-visible {
outline: 2px solid var(--accent-primary);
outline-offset: 2px;
}
&.disabled,
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
Flex Layout
.my-row {
display: flex;
align-items: center;
gap: @spacing-4;
}
.my-row-between {
justify-content: space-between;
}
.my-row-center {
justify-content: center;
}
.my-col {
display: flex;
flex-direction: column;
gap: @spacing-4;
}
Responsive Design
.my-grid {
display: grid;
gap: @spacing-4;
grid-template-columns: 1fr;
@media (min-width: @screen-md) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: @screen-lg) {
grid-template-columns: repeat(3, 1fr);
}
}
Mixins
Useful Mixins
.flex-center() {
display: flex;
align-items: center;
justify-content: center;
}
.text-truncate() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.focus-ring() {
&:focus-visible {
outline: 2px solid var(--accent-primary);
outline-offset: 2px;
}
}
.hover-lift() {
transition: transform 0.15s ease, box-shadow 0.15s ease;
&:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
}
Using Mixins
.my-card {
.hover-lift();
background: var(--bg-secondary);
border-radius: @radius-lg;
}
.my-button {
.flex-center();
.focus-ring();
}
Adding New Styles
1. Choose/Create File
2. Import in index.less
@import 'components/my-category.less';
3. Write Styles
.my-widget { }
.my-widget-header { }
.my-widget-body { }
.my-widget-sm { }
.my-widget-primary { }
4. Build
cd cacao/frontend && npm run build
Checklist
Anti-Patterns
.my-widget {
color: #ffffff;
background: #1a1a2e;
}
.my-widget {
color: var(--text-primary);
background: var(--bg-secondary);
}
.my-widget {
padding: 13px 17px;
font-size: 15px;
}
.my-widget {
padding: @spacing-3 @spacing-4;
font-size: @font-size-base;
}
.my-button:hover {
background: var(--accent-hover);
}
.my-button {
transition: background 0.15s ease;
&:hover {
background: var(--accent-hover);
}
}