withastro/astro · warning

[astro] The "${key}" directive cannot be applied dynamically

Error message

[astro] The "${key}" directive cannot be applied dynamically at runtime. It will not be rendered as an attribute.

Make sure to use the static attribute syntax (`${key}={value}`) instead of the dynamic spread syntax (`{...{ "${key}": value }}`).

What it means

addAttribute renders runtime attribute strings, but compiler directives (STATIC_DIRECTIVES such as class:list, set:html, set:text, is:raw) are compiled away at build time. When one arrives through a dynamic spread, Astro cannot apply it at runtime: it logs this warning and returns '' so nothing broken reaches the HTML. The directive must be written as a static attribute for the compiler to see it.

Source

Thrown at packages/astro/src/runtime/server/render/util.ts:102

}

// A helper used to turn expressions into attribute key/value
// In the compiler, addAttribute is only printed to process attributes of elements
// that may contain dynamic values. We don't need to pass tagName to addAttribute
// on the compiler side because it is used only for custom elements
export function addAttribute(value: any, key: string, shouldEscape = true, tagName = '') {
	if (value == null) {
		return '';
	}

	// Reject attribute names with characters that could break out of the attribute context.
	if (INVALID_ATTR_NAME_CHAR.test(key)) {
		return '';
	}

	// compiler directives cannot be applied dynamically, log a warning and ignore.
	if (STATIC_DIRECTIVES.has(key)) {
		console.warn(`[astro] The "${key}" directive cannot be applied dynamically at runtime. It will not be rendered as an attribute.

Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the dynamic spread syntax (\`{...{ "${key}": value }}\`).`);
		return '';
	}

	// support "class" from an expression passed into an element (#782)
	if (key === 'class:list') {
		const listValue = toAttributeString(clsx(value), shouldEscape);
		if (listValue === '') {
			return '';
		}
		return markHTMLString(` ${key.slice(0, -5)}="${listValue}"`);
	}

	// support object styles for better JSX compat
	if (key === 'style' && !(value instanceof HTMLString)) {
		if (Array.isArray(value) && value.length === 2) {
			return markHTMLString(

View on GitHub (pinned to 52e6c34790)

Solutions

  1. Apply the directive statically at the call site: class:list={...} or set:html={...} instead of spreading
  2. Filter directive keys out of the object before spreading and handle them as explicit static attributes
  3. For dynamic classes use a plain class={...} or a statically written class:list with computed values

Example fix

// before
<div {...{ 'class:list': ['a', { active: isActive }] }} />

// after
<div class:list={['a', { active: isActive }]} />
Defensive patterns

Strategy: validation

Validate before calling

// Split spreadable props from compiler directives before spreading
const DIRECTIVE_KEYS = new Set(['class:list', 'set:html', 'set:text', 'is:raw']);
export function splitDirectives(props: Record<string, unknown>) {
  const spreadable: Record<string, unknown> = {};
  const directives: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(props)) {
    (DIRECTIVE_KEYS.has(k) ? directives : spreadable)[k] = v;
  }
  return { spreadable, directives }; // apply directives as static attributes
}

Prevention

When it happens

Trigger: Spreading an object containing a directive key: <div {...{ 'class:list': [...] }} />, or a wrapper component forwarding arbitrary props ({...rest}) where the props object happens to include set:html, set:text, class:list, or is:raw.

Common situations: Generic wrapper components that spread all props onto an element; porting React forwarding patterns to .astro; prop objects assembled dynamically from data or CMS content.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/a8fcde7f605c5eef. Report an issue: GitHub.