Built-in DDC Cards
Drag & Drop Card includes several helper cards that can be used inside the canvas. These are useful when you want dashboard-specific building blocks without installing another custom card.
HTML Card
The HTML card is for custom markup, CSS, and small scripts. Use it for highly customized dashboard elements, but keep the code focused and avoid turning it into a full application unless that is really needed.

This card is quite revolutionary as it gives users the full flexibility of web-design, look and feel. with this you can create

A custom Media Player CardPersisting Runtime Config in an HTML card
The HTML card can contain its own UI, JavaScript, and local configuration controls. A common pattern is to add a config button inside the HTML card itself, so users can edit widget-specific settings without opening the Drag & Drop Card editor.However, developers should be careful about where those settings are stored.
The Problem
Storing widget settings only in `localStorage` is not durable enough.
`localStorage` survives normal reloads, but it may be removed when the user clears browser cache/site data, changes browser/device, or resets Home Assistant frontend storage.
For settings that should survive a clear cache, the HTML card must write its runtime configuration back into the saved Drag & Drop Card layout.
Recommended Pattern
Store widget-specific settings as a custom field on the `custom:ddc-html-card` config.
Example:
{
"type": "custom:ddc-html-card",
"html": "...",
"css": "...",
"js": "...",
"neo_score_config": {
"entity": "sensor.smarti_hourly_energy_consumed",
"label": "Energy used",
"min": "0",
"max": "10000",
"unit": "kWh"
}
}The HTML card JavaScript should:
Read from config.neo_score_config on startup.
Fall back to localStorage only for migration or emergency recovery.
When the user clicks Apply, write the new values back into the card config.
Update DDC’s wrapper config cache.
Ask DDC to save the layout.
Runtime Objects Available
Inside custom:ddc-html-card JavaScript, developers can use:
root // HTML content root
host // the ddc-html-card element
config // current card config
hass // Home Assistant object
ddc // Drag & Drop Card local dashboard APIThe ddc API exposes helpers such as:
ddc.saveLayout(true);
ddc.settings.get(key);
ddc.settings.set(key, value, { persist: true });For widget-specific config, prefer writing to the card config itself rather than dashboard-level settings.
Durable Save Example
This pattern writes internal widget settings into the saved HTML card config.
const CONFIG_FIELD = 'neo_score_config';
function getCardWrapper() {
return host?.closest?.('.card-wrapper') || null;
}
function cloneJson(value, fallback = null) {
try {
return JSON.parse(JSON.stringify(value));
} catch {
return fallback;
}
}
function readSourceCardConfig() {
const wrap = getCardWrapper();
try {
const parsed = JSON.parse(wrap?.dataset?.cfg || 'null');
if (parsed && typeof parsed === 'object') return parsed;
} catch {}
if (host?.__ddcSourceConfig) {
return cloneJson(host.__ddcSourceConfig, host.__ddcSourceConfig);
}
if (host?._config) {
return cloneJson(host._config, host._config);
}
return cloneJson(config, config) || { type: 'custom:ddc-html-card' };
}
async function saveWidgetConfig(values) {
const wrap = getCardWrapper();
const source = readSourceCardConfig();
const nextConfig = {
...source,
type: source.type || 'custom:ddc-html-card',
[CONFIG_FIELD]: values
};
// Update DDC's runtime/card config references.
try {
config[CONFIG_FIELD] = values;
} catch {}
try {
host.__ddcSourceConfig = cloneJson(nextConfig, nextConfig);
} catch {}
try {
if (host._config && typeof host._config === 'object') {
host._config = {
...host._config,
[CONFIG_FIELD]: values
};
}
} catch {}
try {
if (wrap?.dataset) {
wrap.dataset.cfg = JSON.stringify(nextConfig);
}
} catch {}
// Update responsive layout memory when available.
try {
const cardId = wrap?.dataset?.layoutCardId;
if (cardId && ddc?.card?._updateCardConfigAcrossResponsiveLayouts_) {
ddc.card._updateCardConfigAcrossResponsiveLayouts_(cardId, nextConfig);
}
} catch {}
// Save the layout.
try {
await ddc?.saveLayout?.(true);
} catch {}
// Storage-dashboard persistence fallback, when available.
try {
await ddc?.card?._persistThisCardConfigToStorage_?.();
} catch {}
}Reading the Config on Startup
Always prefer the durable card config first:
function readWidgetConfig() {
if (config?.neo_score_config) {
return config.neo_score_config;
}
// Optional migration fallback only.
try {
return JSON.parse(localStorage.getItem('my-widget-config') || 'null');
} catch {
return null;
}
}Important Limitation
This only survives a clear cache if the Drag & Drop Card layout itself is saved outside browser-local storage.
Durable persistence requires one of:
- DDC backend storage
- Home Assistant Storage dashboard save
- YAML/storage persistence supported by the dashboard setup
If the whole DDC layout exists only in browser-local state, then no HTML-card script can preserve settings after clearing site data.
Best Practices
Use localStorage only as a fallback, not as the source of truth.
Use a namespaced custom config field, for example:
neo_score_config
weather_ring_config
room_panel_configKeep values JSON-safe: strings, numbers, booleans, arrays, and plain objects.
After saving internal config, update:
host.__ddcSourceConfig
host._config
wrap.dataset.cfgThen call:
await ddc.saveLayout(true);For widgets that also store default values in HTML data-* attributes, update the saved HTML string too. This makes exported/imported card JSON easier to inspect and keeps the visual markup aligned with the config object.
Summary
For cache-resistant HTML-card settings:
- Do not rely only on localStorage.
- Store runtime settings in the card config.
- Sync the updated config into DDC’s wrapper cache.
- Save the DDC layout.
- Keep localStorage only as a migration fallback.
Text Card
The text card is for labels, headings, short notes, and rich text blocks. It is useful for dashboard titles, room labels, section headers, and short explanations.
Example:

Icon Card
The icon card is for large visual state indicators. It can react to Home Assistant entity state and is useful for lights, locks, media, climate, security, and status displays.
Table Card
The table card is for compact information layouts. Use it for room summaries, battery lists, schedules, sensor grids, or control rows.
