Polymorphic as prop in Astro
Published: 2023-08-31 15:33:20

---
title: Polymorphic as prop in Astro
published: true
date: 2023-08-23 00:00:00 UTC
tags: 
canonical_url: https://chan.dev/astro-dynamic-tags
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6c287v2bj4vh46mfbffv.png
---

Astro has [dynamic tags](https://docs.astro.build/en/core-concepts/astro-syntax/#dynamic-tags) — a way to take an element or component as a prop.

I had trouble finding the feature because the React community calls this pattern the "polymorphic as prop". _Because we enjoy our pseudo-computer-science bullshit artisinally._

Dynamic tags are simple to implement in Astro.

## No-fuss implementation

1. Take a _capitalized_ `Element` prop as a local variable.
2. Render that prop as a template tag.
3. Take and spread rest-props.
4. Render children using Astro's un-named [`<slot />`](https://docs.astro.build/en/core-concepts/astro-components/#slots).

```
---
const { Element, ...props } = Astro.props;
---

<Element {...props}><slot /></Element>

```

## Destructure and rename the element prop (for convenience or convention)

Uppercase prop names can look out of place in templates. Destructure and rename the `Element` prop in one to provide a more ergonomic/conventional authoring experience.

```
---
const { as: Element, ...props } = Astro.props;
---

<Element {...props}><slot /></Element>

```

Astro templates require that dynamic tags be capitalized. Renaming the element prop is a convent way to follow that requirement while providing a more conventional API to consumers.

## Accept and de-duplicate classes with `class:list`

Astro has a [`class:list` directive](https://docs.astro.build/en/reference/directives-reference/#classlist) for orchestrating dynamic classes. Provide the `class:list` directive an array with both _provided_ and _component_ classes.

`class:list` is smart and automatically removes duplicate classes.

```
---
const {
  as: Element = "div",
  class: providedProps,
  ...props
} = Astro.props;
const componentClasses = "prose prose-slate dark:prose-invert";
---

<Element {...props} class:list={[componentClasses, providedProps]}>
  <slot />
</Element>

```

_Note: `class` needs to be renamed when destructured because values can not be assigned to reserved words._

## Complete with TypeScript interface

This is my completed component, with the TypeScript interface.

Yours needs will likely vary.

```astro
---
interface Props {
  as?: "body" | "main" | "article";
  class?: "string";
}

const {
  as: Element = "div",
  class: providedProps,
  ...props
} = Astro.props;
---

<Element {...props} class:list={[componentClasses, providedProps]}>
  <slot />
</Element>

```

## BEWARE: dynamic tags don't honor hydration client directives

Astro provides [client directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) for hydrating client-side UI. Those don't work with dynamic tags.

If you're using dynamic tags for static layouts — like me — this isn't an issue.

## Takeaways

Astro supports the "polymorphic as prop" pattern popular in React. And the additional standard tooling of TypeScript and [`class:list` directive](https://docs.astro.build/en/reference/directives-reference/#classlist) make it even easier to consistently implement.