Skip to main content

Component

A Component is one component on your published page. It gives you a stable, supported surface for reading and driving that component, so you don't have to reach into the DOM.

You get one from a ComponentSet — via .only(), .first(), .nth(i), or iteration — or as event.target in an event handler.

const hero = experience.findByLocator('hero').only()

hero.componentType // 'cml-text'
hero.cmlLocator // 'hero'
hero.text.setText('Welcome')

Everything on a component falls into one of four groups: identity, attributes and data, capabilities, and events — plus a back-reference to the experience it lives in.

Identity​

Read-only properties describing the component.

PropertyTypeWhat it is
cmlLocatorstringThe name you gave it in Flex, or '' if it has none.
componentTypestringIts type, e.g. 'cml-text'.
cmlIdstringIts internal id, or '' if unset.
capabilitiesCapabilityName[]Which capabilities it carries, e.g. ['text', 'states', 'visibility'].
if (hero.componentType === 'cml-video') hero.media.play()

has(capability)​

has(capability: CapabilityName): boolean

Check for a capability before using it. CapabilityName is 'media' | 'text' | 'states' | 'visibility' | 'pages'.

if (hero.has('media')) hero.media.play()

Optional chaining does the same job more briefly:

hero.media?.play() // does nothing if hero isn't a video

The experience it belongs to​

experience: Component | undefined

Every component points back at the experience it lives in — the same handle connect() gave you — so you can reach experience-level things from a component without passing the handle around or connecting again:

function onCardClick(event) {
// no `experience` in scope — the component brought it with it
event.target.experience.pages.next()
}

Two things it's for. pages, which lives on the experience and nowhere else. And widening a search: a component's own findBy* sees only its own contents, so card.experience.findByLocator('header') searches the whole experience.

It's the same object throughout — your handle for anything the SDK found, and itself on the handle, so you never have to check whether you're holding the root:

const experience = await connect()
const cta = experience.findByLocator('cta').only()

cta.experience === experience // true
experience.experience === experience // true

It's undefined only for a component that isn't inside an experience at all — an element you built yourself and passed to wrap(). Anything the SDK found has one, so component.experience.pages.next() is safe in practice.

Capabilities​

What a component can do depends on its type. A capability namespace exists only when the component carries it; otherwise the property is undefined.

NamespaceOnMethods
mediacml-videoplay(), pause()
textcml-textsetText(text), getText()
statesAny visible elementactivate(name), deactivate(name), toggle(name), list(), current()
visibilityAny visible elementshow(), hide(), isHidden()
pagesThe experiencelist(), current(), goTo(slugOrIndex), next(), previous()
const video = experience.findByTag('cml-video').first()

video?.media.pause()
video?.visibility.hide()

See Capabilities for each one in full.

Attributes​

getAttribute(name: string): string | undefined
setAttribute(name: string, value: string | null): void

Read and write HTML attributes on the component.

const link = experience.findByLocator('cta-link').only()

link.getAttribute('href') // 'https://…', or undefined if absent
link.setAttribute('data-seen', '1')
link.setAttribute('data-seen', null) // passing null removes it

On a set, setAttribute applies to every member and getAttribute reads the first — see how calls fan out.

Two attribute families are off-limits and throw if you try to write them: cml-id, which identifies the component and can't change, and anything starting with editor-, which exists only while authoring and never reaches a published page. Both are readable.

Author data​

getData(key: string): string | undefined
setData(key: string, value: string | null): void

Key-value data attached to an element in Flex's SDK Data inspector. This is the clean way to pass values from the design to your code — a product SKU, a price, a video id — without hard-coding them in the script.

Data lives under a reserved data-sdk- prefix, so getData('color') reads data-sdk-color. That keeps it separate from Ceros' own attributes: you only ever read back what was put there deliberately.

const card = experience.findByLocator('card').only()

card.getData('sku') // 'ABC-123', or undefined if the key isn't set
card.setData('sku', 'XYZ-789')
card.setData('sku', null) // removes it
// a common pattern: read per-element config set by whoever built the design
experience.findByLocator('product').forEach((product) => {
fetchPrice(product.getData('sku')).then((price) => {
product.findByLocator('price').text.setText(price)
})
})

Events​

on(type: SdkEventType, handler: (event: ComponentEvent) => void): Unsubscribe

Subscribe to a runtime event on this component. Returns a function you call to stop listening. Events bubble, so a listener on a container also fires for everything inside it.

const off = hero.on('component.click', (event) => {
console.log('clicked', event.target.cmlLocator)
})

off() // stop listening

Full detail — event types, payload, and patterns — in Events.

Searching inside a component​

The same three lookups as your experience handle, scoped to this component's contents (the component itself is never a match):

findByLocator(locator: string): ComponentSet
findByTag(tag: string): ComponentSet
findById(id: string): ComponentSet
const pricing = experience.findByLocator('pricing').only()

pricing.findByTag('cml-text') // only the text inside the pricing section

See Finding components.

getElement()​

getElement(): Element

The escape hatch to the underlying DOM element, for the rare thing the SDK doesn't cover — measuring layout, reading computed styles, attaching a listener for an event the SDK doesn't expose.

const rect = hero.getElement().getBoundingClientRect()

Prefer the capabilities, properties, and on(...) where they cover what you need: they're the supported surface, and they won't change under you. Writing directly to the element — setting textContent on a text component, for example — can corrupt what Ceros renders.

Runtime only​

Everything a component lets you change — text, visibility, states, attributes, data — changes the live page and nothing else. It is never saved back to your experience, and a reload restores exactly what was published. See Limits.

wrap(element)​

wrap(element: Element): Component

Wraps a DOM element as a Component. Lookups already do this for you, so you'll rarely need it — mainly useful in tests where you have an element in hand.

A wrapped element still finds its own experience, by looking up the page for the experience containing it. That gives you an equivalent handle rather than the identical object your connect() returned, and it's undefined if the element isn't inside an experience at all.