Componentes e tokens para manter consistência visual
Logos oficiais da blipee e variantes para campanhas de sensibilização.
Importe os logos da sua marca. Os logos personalizados mantêm as suas cores originais, independentemente do tema selecionado.
Variantes principais do logo para diferentes contextos de utilização.
Logos especiais para campanhas de awareness.
touch_app Clica num logo para aplicar esse esquema de cores à página
Paletas de cores recomendadas para cada campanha.
Baseado em Material Design 3 com tema escuro. Surface base: #121212 com overlays brancos para elevação.
/* Uso de cores */ background: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface); border: 1px solid var(--md-sys-color-outline-variant); /* Para elementos de destaque */ background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container);
Escala tipográfica baseada em Material Design 3. Sofia Sans para branding, Roboto para texto.
/* Tipografia via classes */ <h1 class="md-typescale-headline-large">Título</h1> <p class="md-typescale-body-medium">Texto</p> /* Ou via CSS variables */ font-family: var(--md-sys-typescale-title-large-font); font-size: var(--md-sys-typescale-title-large-size); font-weight: var(--md-sys-typescale-title-large-weight); line-height: var(--md-sys-typescale-title-large-line-height);
Sistema de espaçamento baseado em múltiplos de 4px para consistência.
/* Padding e margin */ padding: var(--md-sys-spacing-4); /* 16px */ margin-bottom: var(--md-sys-spacing-6); /* 24px */ gap: var(--md-sys-spacing-3); /* 12px */ /* Exemplo de card */ .card { padding: var(--md-sys-spacing-6); margin-bottom: var(--md-sys-spacing-4); }
Em dark theme, elevação é representada por overlays brancos sobre #121212, não por sombras.
Sombras para feedback visual e hierarquia de componentes.
Material Symbols usa tamanhos padronizados.
Áreas mínimas para interação táctil.
/* Usar surface containers para elevação */ .card-level-0 { background: var(--md-sys-color-surface); } .card-level-1 { background: var(--md-sys-color-surface-container-low); } .card-level-2 { background: var(--md-sys-color-surface-container); } .card-level-3 { background: var(--md-sys-color-surface-container-high); } .card-level-4 { background: var(--md-sys-color-surface-container-highest); } /* Sombras para feedback visual */ --md-sys-elevation-1: 0 1px 3px rgba(0, 0, 0, 0.08); /* Cards em repouso */ --md-sys-elevation-2: 0 2px 8px rgba(0, 0, 0, 0.12); /* Hover states */ --md-sys-elevation-3: 0 4px 12px rgba(0, 0, 0, 0.15); /* Menus, dropdowns */ --md-sys-elevation-4: 0 8px 24px rgba(0, 0, 0, 0.2); /* Modais */ --md-sys-elevation-glow: 0 2px 8px rgba(0, 212, 255, 0.3); /* Primary hover */ /* Border radius */ border-radius: var(--md-sys-shape-corner-medium); /* 12px */ border-radius: var(--md-sys-shape-corner-large); /* 16px */ border-radius: var(--md-sys-shape-corner-full); /* pill */ /* Tamanhos de ícones */ font-size: 18px; /* Dense UI, inline */ font-size: 20px; /* Menus, listas */ font-size: 24px; /* Default (icon buttons, nav) */ font-size: 48px; /* Feature icons, empty states */ /* Touch targets */ min-width: 40px; min-height: 40px; /* Standard */ min-width: 44px; min-height: 44px; /* Touch-friendly (WCAG AA) */ min-width: 48px; min-height: 48px; /* WCAG AAA */
Padrões de layout, grids e containers para estruturar páginas.
O container principal limita a largura do conteúdo e centra na página.
.container { max-width: 1200px; margin: 0 auto; padding: var(--md-sys-spacing-6); /* 24px */ }
Use CSS Grid para layouts responsivos com colunas flexíveis.
/* Grid auto-fill para cards responsivos */ .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--md-sys-spacing-4); /* 16px */ } /* Grid fixo de 3 colunas */ .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--md-sys-spacing-4); } /* Layout sidebar + content */ .layout-sidebar { display: grid; grid-template-columns: 280px 1fr; gap: var(--md-sys-spacing-6); /* 24px */ }
/* Row com gap */ .flex-row { display: flex; align-items: center; gap: var(--md-sys-spacing-3); /* 12px */ flex-wrap: wrap; } /* Header pattern (space between) */ .flex-between { display: flex; justify-content: space-between; align-items: center; } /* Centered content */ .flex-center { display: flex; justify-content: center; align-items: center; }
Valores consistentes de padding para diferentes contextos.
| Contexto | Padding | Token |
|---|---|---|
| Botões | 12px 24px | spacing-3 spacing-6 |
| Inputs | 14px 16px | ~spacing-3.5 spacing-4 |
| Cards | 24px | spacing-6 |
| Modal Body | 24px | spacing-6 |
| Modal Header/Footer | 20px 24px | spacing-5 spacing-6 |
| Page Container | 24px | spacing-6 |
| Nav Drawer Items | 14px 16px | ~spacing-3.5 spacing-4 |
| Contexto | Gap | Token |
|---|---|---|
| Ícone + Label (inline) | 8px | spacing-2 |
| Botões em row | 12px | spacing-3 |
| Grid de cards | 16px | spacing-4 |
| Form groups | 20px | spacing-5 |
| Secções de página | 24-32px | spacing-6 a spacing-8 |
Inputs, selects e outros controlos de formulário.
<!-- Form Field --> <div class="form-field"> <label class="form-label">Nome</label> <input type="text" class="form-input" placeholder="Introduza..."> </div> <!-- Select --> <div class="form-field"> <label class="form-label">Fase</label> <select class="form-input form-select"> <option>Selecionar...</option> </select> </div>
Checkbox, Radio, Switch e Slider para seleção e ajuste de valores.
Para seleções múltiplas ou opções on/off independentes.
<!-- Checkbox --> <label class="checkbox-label"> <input type="checkbox" class="checkbox"> <span>Label text</span> </label> /* CSS */ .checkbox { width: 20px; height: 20px; accent-color: var(--md-sys-color-primary); cursor: pointer; } .checkbox-label { display: flex; align-items: center; gap: 12px; cursor: pointer; }
Para seleção única entre opções mutuamente exclusivas.
<!-- Radio Group --> <div class="radio-group"> <label class="radio-label"> <input type="radio" name="group" class="radio" checked> <span>Opção 1</span> </label> <label class="radio-label"> <input type="radio" name="group" class="radio"> <span>Opção 2</span> </label> </div> /* CSS */ .radio { width: 20px; height: 20px; accent-color: var(--md-sys-color-primary); }
Toggle para ativar/desativar funcionalidades.
<!-- Switch --> <label class="switch-label"> <input type="checkbox" class="switch-input"> <span class="switch-track"> <span class="switch-thumb"></span> </span> <span>Label</span> </label> /* CSS */ .switch-label { display: flex; align-items: center; gap: 12px; cursor: pointer; } .switch-input { display: none; } .switch-track { width: 52px; height: 32px; background: var(--md-sys-color-surface-container-highest); border-radius: 16px; position: relative; transition: background 0.2s; } .switch-thumb { width: 24px; height: 24px; background: var(--md-sys-color-outline); border-radius: 50%; position: absolute; top: 4px; left: 4px; transition: all 0.2s; } .switch-input:checked + .switch-track { background: var(--md-sys-color-primary); } .switch-input:checked + .switch-track .switch-thumb { background: var(--md-sys-color-on-primary); left: 24px; }
Para seleção de valores dentro de um intervalo.
<!-- Slider --> <input type="range" class="slider" min="0" max="100" value="50"> /* CSS */ .slider { width: 100%; height: 4px; background: var(--md-sys-color-surface-container-highest); border-radius: 2px; outline: none; -webkit-appearance: none; } .slider::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; background: var(--md-sys-color-primary); border-radius: 50%; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
Modais, diálogos de confirmação e menus dropdown.
Estrutura padrão com header, body e footer.
<!-- Modal Structure --> <div class="modal-overlay"> <div class="modal"> <div class="modal-header"> <h2 class="modal-title">Título</h2> <button class="modal-close">×</button> </div> <div class="modal-body">...</div> <div class="modal-footer"> <button class="btn btn-text">Cancelar</button> <button class="btn btn-filled">Confirmar</button> </div> </div> </div> /* CSS */ .modal-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 1000; } .modal { background: var(--md-sys-color-surface-container-high); border-radius: var(--md-sys-shape-corner-extra-large); /* 28px */ max-width: 560px; width: 90%; max-height: 90vh; overflow: hidden; } .modal-header { padding: 20px 24px; border-bottom: 1px solid var(--md-sys-color-outline-variant); } .modal-body { padding: 24px; } .modal-footer { padding: 16px 24px; border-top: 1px solid var(--md-sys-color-outline-variant); display: flex; justify-content: flex-end; gap: 12px; }
Esta ação não pode ser desfeita.
Lista de ações ou opções.
<!-- Menu --> <div class="menu"> <button class="menu-item"> <span class="material-symbols-rounded">edit</span> Editar </button> <div class="menu-divider"></div> <button class="menu-item danger"> <span class="material-symbols-rounded">delete</span> Eliminar </button> </div> /* CSS */ .menu { background: var(--md-sys-color-surface-container); border-radius: 12px; min-width: 180px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); overflow: hidden; } .menu-item { width: 100%; padding: 12px 16px; display: flex; align-items: center; gap: 12px; background: none; border: none; color: var(--md-sys-color-on-surface); cursor: pointer; } .menu-item:hover { background: var(--md-sys-color-surface-container-high); } .menu-divider { height: 1px; background: var(--md-sys-color-outline-variant); margin: 4px 0; }
Listas de items com diferentes variantes e layouts.
<!-- Single Line List --> <div class="list-item"> <span class="material-symbols-rounded">inbox</span> <span class="list-item-text">Inbox</span> <span class="badge">3</span> </div> <!-- Two Line List --> <div class="list-item-two-line"> <div class="avatar">JD</div> <div class="list-item-content"> <span class="list-item-title">Título</span> <span class="list-item-subtitle">Subtítulo</span> </div> <button class="icon-btn">...</button> </div> /* CSS */ .list-item { display: flex; align-items: center; gap: 16px; padding: 12px 16px; cursor: pointer; } .list-item:hover { background: var(--md-sys-color-surface-container-high); } .list-item-content { flex: 1; min-width: 0; } .list-item-title { display: block; font-weight: 500; } .list-item-subtitle { display: block; font-size: 13px; color: var(--md-sys-color-on-surface-variant); }
Separador visual entre secções ou items.
Conteúdo acima
Conteúdo abaixo
<!-- Full width divider --> <div class="divider"></div> <!-- Inset divider (for lists) --> <div class="divider-inset"></div> /* CSS */ .divider { height: 1px; background: var(--md-sys-color-outline-variant); } .divider-inset { height: 1px; background: var(--md-sys-color-outline-variant); margin-left: 56px; /* icon + gap */ }
| Nome | Tipo | Valor | Ações | ||
|---|---|---|---|---|---|
| João Silva | joao@email.com | Showroom | €12,500 | more_vert | |
| Maria Santos | maria@email.com | Retail | €8,750 | more_vert | |
| Pedro Costa | pedro@email.com | Showroom | €23,100 | more_vert |
<!-- Data Table --> <div class="data-table-container"> <table class="data-table"> <thead> <tr> <th class="checkbox-cell"> <input type="checkbox"> </th> <th class="sortable sorted"> Nome <span class="sort-icon">arrow_upward</span> </th> <th class="numeric">Valor</th> <th class="actions">Ações</th> </tr> </thead> <tbody> <tr> <td>...</td> </tr> <tr class="selected">...</tr> </tbody> </table> <div class="data-table-footer"> <span>1-3 de 24</span> <div class="data-table-pagination">...</div> </div> </div> /* Classes especiais */ .numeric { text-align: right; } .sortable { cursor: pointer; } .selected { background: var(--md-sys-color-primary-container); }
Para filtros, tags e indicadores de estado.
<!-- Chip --> <button class="chip">Filtro</button> <button class="chip selected">Selecionado</button> <!-- Badges --> <span class="badge badge-success">Ativo</span> <span class="badge badge-warning">Pendente</span> <span class="badge badge-error">Cancelado</span>
Containers para agrupar conteúdo relacionado.
<!-- Card básico --> <div class="card"> <div class="card-title"> <span class="material-symbols-rounded">icon</span> Título do Card </div> <!-- Conteúdo --> </div> /* CSS do Card */ .card { background: var(--md-sys-color-surface-container); border-radius: var(--md-sys-shape-corner-large); padding: var(--md-sys-spacing-6); }
Cards para métricas principais com valor, trend e sparkline opcional.
<!-- KPI Card --> <div class="kpi-card"> <div class="kpi-label">Receita Total</div> <div class="kpi-value">€124.5K</div> <div class="kpi-trend positive"> <span class="material-symbols-rounded">trending_up</span> +12.5% vs mês anterior </div> </div> /* Trend colors */ .kpi-trend.positive { color: var(--md-sys-color-success); } .kpi-trend.negative { color: var(--md-sys-color-error); } .kpi-trend.neutral { color: var(--md-sys-color-on-surface-variant); }
Cards compactos para estatísticas em grelha.
<!-- Stat Card --> <div class="stat-card primary"> <div class="stat-value">156</div> <div class="stat-label">Novos Leads</div> </div> /* Variantes: .primary, .secondary, .tertiary, .success */
Cards para apresentar valores monetários ou métricas importantes.
<!-- Value Card with Progress --> <div class="value-card"> <div class="value-card-header"> <span>Meta Mensal</span> <span class="percentage">78%</span> </div> <div class="value-card-amount"> €78,400 <span>/ €100,000</span> </div> <div class="progress-bar"> <div style="width: 78%"></div> </div> </div>
Cards para representar entidades como leads, clientes, oportunidades.
<!-- Lead Card --> <div class="entity-card"> <div class="entity-header"> <div class="avatar">JS</div> <div class="entity-info"> <div class="entity-name">João Silva</div> <div class="entity-company">Empresa ABC</div> </div> </div> <div class="entity-meta">...</div> </div> <!-- Card com borda lateral colorida --> .card-accent { border-left: 4px solid var(--md-sys-color-primary); }
Cards com ações ou estados interativos.
<!-- Clickable Card --> <div class="card card-clickable"> <!-- hover: background mais escuro, lift --> </div> <!-- Status Indicator --> <div class="status-dot online"></div> .status-dot { width: 12px; height: 12px; border-radius: 50%; } .status-dot.online { background: var(--md-sys-color-success); animation: pulse 2s infinite; } <!-- Empty State --> <div class="card card-empty"> <!-- border: 2px dashed outline-variant --> </div>
Gráficos e visualizações de dados usando CSS puro e SVG. Ideais para dashboards e analytics.
<!-- Bar Chart Vertical --> <div class="bar-chart"> <div class="bar-chart-item"> <div class="bar-chart-bar" style="height: 60%;"> <span>60</span> </div> <span class="bar-chart-label">Jan</span> </div> <!-- Variantes: .success, .secondary, .tertiary --> </div>
<!-- Bar Chart Horizontal --> <div class="bar-chart-horizontal"> <div class="bar-chart-horizontal-item"> <span class="bar-chart-horizontal-label">Label</span> <div class="bar-chart-horizontal-track"> <div class="bar-chart-horizontal-bar" style="width: 85%;"> <span>85%</span> </div> </div> <span class="bar-chart-horizontal-value">€42.5K</span> </div> </div>
<!-- Line Chart com SVG --> <div class="line-chart"> <svg viewBox="0 0 400 150"> <!-- Grid lines --> <line class="line-chart-grid" x1="0" y1="75" x2="400" y2="75"/> <!-- Area fill --> <path class="line-chart-area" d="M0,120 L66,90 ... L400,150 L0,150 Z"/> <!-- Line --> <polyline class="line-chart-line" points="0,120 66,90 133,100..."/> <!-- Dots --> <circle class="line-chart-dot" cx="0" cy="120" r="4"/> </svg> </div>
<!-- Area Chart com múltiplas séries --> <div class="area-chart"> <svg viewBox="0 0 400 150"> <!-- Área preenchida --> <path class="area-chart-area primary" d="M0,130 L66,100 ... L400,150 L0,150 Z"/> <!-- Linha --> <polyline class="area-chart-line primary" points="0,130 66,100..."/> </svg> </div> /* Variantes: .primary, .secondary, .tertiary */
<!-- Stacked Bar Chart Vertical --> <div class="stacked-bar-chart"> <div class="stacked-bar-item"> <span class="stacked-bar-total">85</span> <div class="stacked-bar-stack" style="height: 85%;"> <div class="stacked-bar-segment primary" style="height: 47%;">40</div> <div class="stacked-bar-segment secondary" style="height: 29%;">25</div> </div> <span class="stacked-bar-label">Jan</span> </div> </div>
<!-- Stacked Bar Chart Horizontal --> <div class="stacked-bar-horizontal"> <div class="stacked-bar-horizontal-item"> <span class="stacked-bar-horizontal-label">Q1</span> <div class="stacked-bar-horizontal-stack"> <div class="stacked-bar-horizontal-segment primary" style="width: 40%;">€40K</div> <div class="stacked-bar-horizontal-segment secondary" style="width: 30%;">€30K</div> </div> <span class="stacked-bar-horizontal-total">€90K</span> </div> </div>
<!-- Mixed Chart (Bar + Line) --> <div class="mixed-chart"> <!-- Barras no fundo --> <div class="mixed-chart-bars"> <div class="mixed-chart-bar" style="height: 50%;"></div> </div> <!-- Linha sobreposta --> <svg viewBox="0 0 400 150"> <polyline class="mixed-chart-line" points="33,95 100,70..."/> <circle class="mixed-chart-dot" cx="33" cy="95" r="5"/> </svg> <div class="mixed-chart-labels">...</div> </div>
<!-- Donut Chart com SVG --> <svg class="donut-chart-svg" viewBox="0 0 160 160"> <!-- stroke-dasharray: segmento circunferência --> <!-- Circunferência = 2 * π * r = 376.99 (r=60) --> <!-- Segmento 45% = 0.45 * 376.99 = 169.65 --> <circle class="donut-chart-segment" cx="80" cy="80" r="60" style="stroke: var(--md-sys-color-primary); stroke-dasharray: 169.65 376.99; stroke-dashoffset: 0;"/> <!-- Próximo segmento: offset = -soma anteriores --> </svg>
<!-- Gauge Chart (semi-círculo) --> <!-- Arco total = π * r = 188.5 (r=60) --> <!-- 75% = 0.75 * 188.5 = 141.37 --> <div class="gauge-chart"> <svg viewBox="0 0 160 160"> <!-- Background arc --> <path class="gauge-chart-bg" d="M 20,80 A 60,60 0 0,1 140,80"/> <!-- Fill arc --> <path class="gauge-chart-fill" d="M 20,80 A 60,60 0 0,1 140,80" style="stroke-dasharray: 141.37 188.5;"/> </svg> <span class="gauge-chart-value">75%</span> </div>
<!-- Sparkline --> <div class="sparkline success"> <svg width="80" height="32" viewBox="0 0 80 32"> <path class="sparkline-area" d="M0,28 L13,22 ... L80,32 L0,32 Z"/> <polyline class="sparkline-line" points="0,28 13,22 26,24..."/> </svg> </div> /* Variantes: .success (verde), .error (vermelho), default (primary) */
<!-- Chart Legend --> <div class="chart-legend"> <div class="chart-legend-item"> <div class="chart-legend-dot" style="background: var(--md-sys-color-primary);"></div> <span class="chart-legend-text">Label</span> </div> </div>
<!-- Heatmap --> <div class="heatmap"> <div class="heatmap-row"> <span class="heatmap-label">09:00</span> <div class="heatmap-cell level-3">12</div> <div class="heatmap-cell level-5">24</div> <!-- level-0 a level-5 para intensidade --> </div> </div> /* Níveis de intensidade */ .level-0 { background: surface; } .level-1 { background: rgba(0, 212, 255, 0.2); } .level-2 { background: rgba(0, 212, 255, 0.4); } .level-3 { background: rgba(0, 212, 255, 0.6); } .level-4 { background: rgba(0, 212, 255, 0.8); } .level-5 { background: var(--md-sys-color-primary); }
<!-- Treemap com CSS Grid --> <div class="treemap" style="grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr;"> <!-- Item grande (2 linhas) --> <div class="treemap-item primary" style="grid-row: span 2;"> <span class="treemap-item-label">Label</span> <div> <span class="treemap-item-value">€49.5K</span> <span class="treemap-item-percent">45%</span> </div> </div> <!-- Variantes: .primary, .secondary, .tertiary, .success, .surface --> </div>
<!-- Funnel Chart --> <div class="funnel-chart"> <div class="funnel-stage"> <span class="funnel-stage-label">Visitas</span> <div class="funnel-bar-container"> <div class="funnel-bar" style="width: 100%; background: var(--md-sys-color-primary);"> <span class="funnel-bar-value">1,250</span> </div> </div> <span class="funnel-stage-rate">100%</span> </div> <!-- Conector --> <div class="funnel-connector"> <span class="material-symbols-rounded"> arrow_downward </span> </div> <!-- Repetir para cada estágio --> </div>
Gráficos interativos usando ApexCharts. Cada tipo suporta múltiplas opções de estilo configuráveis no painel do widget.
// Line Chart - Opções de estilo { line_style: 'smooth', // smooth, straight, stepline, dashed stroke_width: 2, show_markers: true, marker_size: 4, show_data_labels: false, zoomable: false, stacked: false, stacked_100: false, connect_null_data: false }
// Area Chart - Opções de estilo { line_style: 'gradient', // basic, straight, stepline, gradient stroke_width: 2, fill_opacity: 40, // 0-100% show_markers: false, stacked: true, github_style: false // contribution graph style }
// Column Chart - Opções de estilo { show_data_labels: true, data_labels_position: 'top', // center, top stacked: false, distributed: false, // different color per bar negative_colors: true, border_radius: 4, column_width: '60%', gradient_fill: false, pattern_fill: false }
// Bar Chart - Opções de estilo { show_data_labels: true, stacked: false, distributed: true, reversed: false, border_radius: 4, bar_height: '70%' }
// Mixed Chart - Opções de estilo { mixed_style: 'line_column', // line_column, line_area, line_scatter multiple_yaxis: true, stroke_width: 3, show_legend: true, legend_position: 'bottom' }
// Range Area - Opções de estilo { combo: true, // show average line line_name: 'Average', fill_opacity: 24, show_legend: true }
// Timeline - Opções de estilo { group_rows: true, distributed: true, dumbbell: false, bar_height: '50%', border_radius: 4 }
// Pie Chart - Opções de estilo { monochrome: false, monochrome_color: '#00d4ff', gradient: false, pattern: false, show_data_labels: true, show_percent: true, show_legend: true }
// Donut Chart - Opções de estilo { semi_donut: false, // 180° half donut donut_size: '70%', show_center_labels: true, center_label: 'Total', monochrome: false, gradient: true }
// Radial Bar - Opções de estilo { semi_circle: false, // 180° gauge custom_angle: false, start_angle: -135, end_angle: 135, hollow_size: '70%', gradient: true, stroked: false, show_value: true }
// Polar Area - Opções de estilo { monochrome: false, fill_opacity: 80, show_data_labels: false, show_yaxis: true, show_legend: true }
// Radar Chart - Opções de estilo { polygon_fill: true, fill_opacity: 40, stroke_width: 2, show_markers: true, marker_size: 4, show_yaxis: false }
// Candlestick - Opções de estilo { combo: false, // show moving average line line_name: 'MA', upward_color: '#4caf50', downward_color: '#f44336', rotated_labels: false }
// Box Plot - Opções de estilo { horizontal: false, show_outliers: true, upper_color: '#00d4ff', lower_color: '#ccc2dc' }
// Scatter Chart - Opções de estilo { datetime_xaxis: false, marker_size: 10, marker_shape: 'circle', // circle, square x_axis_title: 'X', y_axis_title: 'Y' }
// Bubble Chart - Opções de estilo { datetime_xaxis: false, effect_3d: true, min_bubble_radius: 10, max_bubble_radius: 50, fill_opacity: 80 }
// Heatmap - Opções de estilo { multiple_colors: false, flipped: false, color_range: false, rounded: false, radius: 4, show_data_labels: true }
// Treemap - Opções de estilo { distributed: true, enable_shades: true, color_range: false, border_radius: 4, show_data_labels: true, show_values: true }
// Sparkline - Opções de estilo { type: 'line', // line, area, bar color: '#00d4ff', curve: 'smooth', // smooth, straight, stepline stroke_width: 2, fill: true, gradient: true }
Widgets para exibição de dados tabulares, logs e conteúdo personalizado.
// Data Table - Opções de estilo { columns: ['timestamp', 'device', 'metric', 'value'], show_header: true, striped: true, compact: false, max_rows: 10, sortable: true, show_pagination: false } // Dados esperados: array de readings [ { recorded_at: '2024-01-15T14:32:15', metric_key: 'temperature', value_number: 23.5 }, ... ]
// Logs Widget - Opções de estilo { max_lines: 50, show_timestamp: true, show_level: true, auto_scroll: true, filter_level: 'all', // all, error, warning, info, debug monospace: true, wrap_lines: false } // Dados esperados: array de log entries [ { timestamp: '14:32:15', level: 'info', message: 'Device connected' }, ... ]
// Canvas Widget - Elementos configuráveis { elements: [ { type: 'circle', // circle, rect, text, image, line x: 100, y: 100, radius: 20, fill: 'var(--md-sys-color-primary)', data_binding: { device_id: 'sensor-01', metric_key: 'temperature', display: 'value' // value, status, color } }, { type: 'text', x: 100, y: 130, content: 'Sensor 01', font_size: 12, color: 'var(--md-sys-color-on-surface-variant)' }, { type: 'image', x: 0, y: 0, width: 400, height: 300, src: '/images/floor-plan.png' } ], background: 'transparent', grid: false }
Componentes para seleção de datas e horas seguindo os padrões Material Design.
<!-- Date Picker --> <div class="date-picker"> <div class="date-picker-header"> <span class="date-picker-title">Janeiro 2026</span> <div class="date-picker-nav">...</div> </div> <div class="date-picker-weekdays">...</div> <div class="date-picker-days"> <button class="date-picker-day">1</button> <button class="date-picker-day selected">15</button> <button class="date-picker-day today">29</button> <button class="date-picker-day other-month">1</button> </div> <div class="date-picker-actions">...</div> </div>
<!-- Time Picker --> <div class="time-picker"> <div class="time-picker-display"> <input class="time-picker-input" value="10"> <span class="time-picker-separator">:</span> <input class="time-picker-input" value="30"> <div class="time-picker-period"> <button class="active">AM</button> <button>PM</button> </div> </div> </div>
Componente de pesquisa com sugestões e estados interativos.
<!-- Search Input --> <div class="search-container"> <div class="search-input-wrapper"> <span class="material-symbols-rounded">search</span> <input class="search-input" placeholder="Pesquisar..."> <button class="search-clear">...</button> </div> </div>
<!-- Search com Sugestões --> <div class="search-container"> <div class="search-input-wrapper">...</div> <div class="search-suggestions"> <div class="search-suggestion"> <span class="material-symbols-rounded">history</span> <span class="search-suggestion-text"> <mark>blip</mark>ee showroom </span> </div> </div> </div>
Bottom sheets e side sheets para conteúdo secundário ou ações contextuais.
<!-- Bottom Sheet --> <div class="bottom-sheet"> <div class="bottom-sheet-handle"></div> <div class="bottom-sheet-header"> <span class="bottom-sheet-title">Opções</span> <button class="btn btn-icon">...</button> </div> <div class="bottom-sheet-content"> <!-- List items --> </div> </div>
Conteúdo principal da página com informações importantes...
O side sheet aparece à direita para detalhes adicionais.
Cliente: João Silva
Email: joao@email.com
Telefone: +351 912 345 678
Criado: 29 Jan 2026
<!-- Side Sheet --> <div class="side-sheet-demo"> <div class="side-sheet-content"> <!-- Conteúdo principal --> </div> <div class="side-sheet"> <div class="side-sheet-title">Detalhes</div> <!-- Conteúdo do sheet --> </div> </div>
Componente para apresentar múltiplos itens numa sequência navegável.
<!-- Carousel --> <div class="carousel"> <div class="carousel-track"> <div class="carousel-slide">Slide 1</div> <div class="carousel-slide">Slide 2</div> <div class="carousel-slide">Slide 3</div> </div> <div class="carousel-controls"> <button class="carousel-btn"> <span class="material-symbols-rounded">chevron_left</span> </button> <button class="carousel-btn"> <span class="material-symbols-rounded">chevron_right</span> </button> </div> <div class="carousel-indicators"> <div class="carousel-indicator active"></div> <div class="carousel-indicator"></div> </div> </div> // JavaScript let currentSlide = 0; function moveCarousel(dir) { const track = document.querySelector('.carousel-track'); const slides = track.children.length; currentSlide = (currentSlide + dir + slides) % slides; track.style.transform = `translateX(-${currentSlide * 100}%)`; updateIndicators(); }
Progress indicators, toasts, estados de loading e feedback visual.
Determinate (50%)
Indeterminate
<!-- Linear Progress --> <div class="progress-linear"> <div class="progress-linear-bar" style="width: 50%"></div> </div> <!-- Indeterminate --> <div class="progress-linear progress-linear-indeterminate"> <div class="progress-linear-bar"></div> </div> <!-- Circular Progress --> <svg class="progress-circular" viewBox="0 0 50 50"> <circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle> </svg> /* CSS */ .progress-linear { height: 4px; background: var(--md-sys-color-surface-container-highest); border-radius: 2px; overflow: hidden; } .progress-linear-bar { height: 100%; background: var(--md-sys-color-primary); }
<!-- Toast --> <div class="toast"> Alterações guardadas <button class="toast-action">Desfazer</button> </div> /* CSS */ .toast { display: inline-flex; align-items: center; gap: 12px; padding: 14px 16px; background: var(--md-sys-color-inverse-surface); color: var(--md-sys-color-inverse-on-surface); border-radius: var(--md-sys-shape-corner-small); }
Efeito de feedback visual ao clicar em elementos interativos.
/* Ripple CSS */ .ripple-container { position: relative; overflow: hidden; } .ripple { position: absolute; border-radius: 50%; background: currentColor; opacity: 0.2; transform: scale(0); animation: ripple-effect 0.6s ease-out; } @keyframes ripple-effect { to { transform: scale(4); opacity: 0; } } /* JavaScript */ function createRipple(event) { const button = event.currentTarget; const ripple = document.createElement('span'); const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); ripple.style.width = ripple.style.height = size + 'px'; ripple.style.left = event.clientX - rect.left - size/2 + 'px'; ripple.style.top = event.clientY - rect.top - size/2 + 'px'; ripple.classList.add('ripple'); button.appendChild(ripple); ripple.addEventListener('animationend', () => ripple.remove()); }
Indicador visual de foco para acessibilidade (navegação por teclado).
/* Focus Ring CSS */ .focus-ring:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; } /* Aplicar globalmente */ :focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; } /* Nota: :focus-visible só aparece na navegação por teclado, não ao clicar com o rato - melhor UX */
Nenhum item encontrado
Crie o primeiro item para começar.
<!-- Empty State --> <div class="empty-state"> <span class="material-symbols-rounded">inbox</span> <h3>Nenhum item encontrado</h3> <p>Crie o primeiro item para começar.</p> <button class="btn btn-filled">Criar Novo</button> </div> /* CSS */ .empty-state { text-align: center; padding: 40px 20px; color: var(--md-sys-color-on-surface-variant); } .empty-state .material-symbols-rounded { font-size: 64px; opacity: 0.3; margin-bottom: 16px; }
Mensagens breves na parte inferior do ecrã com ação opcional.
<!-- Snackbar simples --> <div class="snackbar"> <span class="snackbar-text">Mensagem</span> </div> <!-- Com ação --> <div class="snackbar"> <span class="snackbar-text">Ficheiro eliminado</span> <button class="snackbar-action">Anular</button> </div> <!-- Com ícone e fechar --> <div class="snackbar with-icon"> <span class="material-symbols-rounded snackbar-icon">check_circle</span> <span class="snackbar-text">Sucesso!</span> <button class="snackbar-close">...</button> </div>
Dicas contextuais que aparecem ao passar o rato sobre elementos.
<!-- Tooltip simples --> <div class="tooltip-trigger"> <button>...</button> <div class="tooltip top">Texto</div> </div> <!-- Posições: .top, .bottom, .left, .right --> <!-- Rich tooltip --> <div class="tooltip tooltip-rich bottom"> <div class="tooltip-rich-title">Título</div> <div class="tooltip-rich-text">Descrição...</div> </div>