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.
| Property | Type | What it is |
|---|---|---|
cmlLocator | string | The name you gave it in Flex, or '' if it has none. |
componentType | string | Its type, e.g. 'cml-text'. |
cmlId | string | Its internal id, or '' if unset. |
capabilities | CapabilityName[] | 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.
| Namespace | On | Methods |
|---|---|---|
media | cml-video | play(), pause() |
text | cml-text | setText(text), getText() |
states | Any visible element | activate(name), deactivate(name), toggle(name), list(), current() |
visibility | Any visible element | show(), hide(), isHidden() |
pages | The experience | list(), 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.