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.
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
<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.
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.
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
<span class="absolute min-w-300"></span>
<span class="absolute min-w-300 bg-primary text-white"></span>v1 limitation: Marmo components only
Marmo's transform only accepts Marmo components in v1.
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).spanThis 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.
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:
<Alert className={inline ? "inline" : "block"} />For reusable display choices, use a variant instead:
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
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
<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>