Transform


Transform renders an existing Marmo component as another intrinsic element. It keeps the original class logic and $ props, while TypeScript validates props against the selected element.

Features
works with any Marmo component
automatically infers the new element's props
preserves the types and class logic of the original component

Inline transformation

Pass $_as to render a Marmo component as a different intrinsic element. Use it primarily for semantic polymorphism: for example, rendering text as a label or a button-styled component as a link.

solidjs - javascript
import ma from '@marmo/solid'
 
const MyButton = ma.button`
  absolute
  min-w-300
`
 
const MyComponent = () => (
  <>
    <MyButton $_as="a" href="/settings">
      Settings
    </MyButton>
    <MyButton>Save</MyButton>
  </>
)

Result

output.html
<a href="/settings" class="absolute min-w-300">Settings</a>
<button class="absolute min-w-300">Save</button>

The selected element's props are inferred automatically. $ props from the source Marmo component are preserved, while ordinary props are validated against the transformed intrinsic element.

react - javascript
const Example = () => (
  <>
    <Text $_as="label" htmlFor="email">
      Email
    </Text>
    <Text $_as="span" className="text-sm">
      Inline information
    </Text>
  </>
)

Builder API

Use the builder API when you want a reusable transformed component, or add more classes with a tagged template.

react - javascript
import ma from '@marmo/react'
 
const MyButton = ma.button`
  absolute min-w-300
`
 
const TransformButton = ma.transform(MyButton).span
 
const TransformButtonWithClasses = ma.transform(MyButton).span`
  bg-primary
  text-white
`
 
const MyComponent = () => {
  return (
    <>
      <TransformButton />
      <TransformButtonWithClasses />
    </>
  )
}

Result

output.html
<span class="absolute min-w-300"></span>
<span class="absolute min-w-300 bg-primary text-white"></span>

v1 limitation: Marmo components only

No support for non-Marmo components yet

Marmo's transform only accepts Marmo components in v1.

TSX
const SomeComponent = () => <div className="pt-10" />
 
// ❌ does not work:
const InvalidMarmoComponent = ma.transform(SomeComponent).span``
 
// Extending first creates a Marmo component, so this is accepted:
const MarmoComponent = ma.extend(SomeComponent)``
const TransformedComponent = ma.transform(MarmoComponent).span

This workaround does not preserve arbitrary rendering when transformed. ma.extend(SomeComponent) preserves SomeComponent while it remains extended, but transforming that Marmo component renders the selected intrinsic element directly. Component internals, hard-coded classes that were not passed through Marmo, and other rendering behavior from SomeComponent are not retained. Prefer transforming a component originally created with Marmo.

Dynamic transformations

The value of $_as can be derived from props or reactive state.

react - javascript
import { useState } from 'react'
import ma from '@marmo/react'
 
const Alert = ma.div`rounded-box p-4`
 
const Example = () => {
  const [inline, setInline] = useState(false)
 
  return (
    <>
      <button onClick={() => setInline((value) => !value)}>Toggle</button>
      <Alert $_as={inline ? 'span' : 'div'}>Status</Alert>
    </>
  )
}

Changing $_as replaces the underlying DOM element. This can reset refs, focus, text selection, DOM-local state, and nested component state. Prefer keeping the semantic element stable when only its appearance or layout changes:

react
<Alert className={inline ? "inline" : "block"} />

For reusable display choices, use a variant instead:

JavaScript
const Alert = ma.div.variants({
  variants: {
    $display: {
      block: 'block',
      inline: 'inline'
    }
  }
})

Server rendering and hydration

Dynamic transformations are SSR-safe when the server render and the first client render resolve to the same intrinsic element. A value derived from stable props is safe, but a value read directly from window.matchMedia, localStorage, or Math.random() can cause a hydration mismatch.

Use a deterministic initial value shared by the server and client, then update it after hydration when necessary. Marmo does not suppress or repair hydration mismatches caused by application state.

Transform variants

react - javascript
import ma from '@marmo/react'
 
const VariantDiv = ma.div.variants({
  base: 'absolute min-w-300',
  variants: {
    $size: {
      sm: 'w-10 h-10',
      md: 'w-20 h-20'
    }
  },
  defaultVariants: {
    $size: 'md'
  }
})
 
const Main = ma.transform(VariantDiv).main`
  text-neutral
  tracking-wide
`
 
const MyComponent = () => {
  return (
    <>
      <Main $size="sm" />
      <Main $size="md" />
    </>
  )
}

Result

output.html
<main class="absolute min-w-300 w-10 h-10 text-neutral tracking-wide"></main>
<main class="absolute min-w-300 w-20 h-20 text-neutral tracking-wide"></main>