Appearance
Engine Styling System
How Creative-Engine components apply CSS to rendered creatives.
Core pattern: CSS-in-JS via styles() computed
Every block component in Creative-Engine defines a styles() computed property that returns a JavaScript object. This object maps CSS selectors to style objects:
javascript
styles() {
return {
[this.toClassSelector(formBlock)]: {
position: 'relative',
display: 'flex',
borderRadius: formProps.style.borderRadius,
...getBackgroundProperties(formProps),
...getBorderProperties({ border: formProps.border, borderStyle: formProps.borderStyle }),
},
'.some-child-element': {
fontSize: '14px',
color: '#000',
},
// Pseudo-classes
[`${this.toClassSelector(inputField)}:hover`]: pseudoClassStyles.hover,
// Pseudo-elements
'.dropdown-input-container::after': {
content: '""',
position: 'absolute',
},
// @keyframes
'@keyframes loading-dots': {
'0%, 80%, 100%': { opacity: 0.3, transform: 'translateY(0)' },
'40%': { opacity: 1, transform: 'translateY(-4px)' },
},
}
}The engine's runtime processes this object and generates actual <style> tags in the creative's DOM. This keeps all styling in JavaScript and avoids static CSS files.
Why this matters: no <style> blocks in .vue files
CRITICAL: Engine components must NEVER use <style> or <style scoped> blocks. Vite extracts these into separate .css files during the production build. This breaks the build pipeline because:
- The
injectTag.tspost-processor usesassets[0]to find the engine JS bundle - A
.cssfile (e.g.creative-engine-C9e3UedP.css) sorts alphabetically beforecreative-engine.js - So
assets[0]picks the CSS file instead of the JS - The CSS content gets injected into
stub.jsascreativeScripts - When loaded, CSS inside a
<script>tag causesSyntaxError: Unexpected token '{'
The rule is simple: All styling goes through styles(). No exceptions.
Class name system
Block components get scoped class names via this.blockClassNames. These are unique per block instance:
javascript
const { formBlock, innerWrap, inputField, submitButton } = this.blockClassNamesUse this.toClassSelector(name) to convert a class name to a CSS selector (prefixes with .).
Use this.toMultiClassSelector(name1, name2, ':hover') for comma-separated selectors.
For non-scoped elements (children that don't need unique class names), use plain string selectors:
javascript
'.dropdown-input-container select': { ... }
'.checkbox-option-label': { ... }Style helper functions
Common style properties are built using shared helpers imported from the engine:
| Helper | Purpose |
|---|---|
getBackgroundProperties(block) | Background color/gradient from block config |
getBorderProperties({ border, borderStyle }) | Border width/style/color |
getBoxShadowProperties({ boxShadow, boxShadowSettings }) | Box shadow |
getPaddingProperties(style) | Padding from style object |
getMarginProperties(style) | Margin from style object |
getPositionAsPx(style) | Top/right/bottom/left as px |
getSizeProperties({ sizeStyle }, responsive) | Width/height |
getFlexAlign(alignment) | Converts 'left'/'center'/'right' to flex values |
getTransformRotation(degrees) | CSS rotate transform |
getTransitionString(transition) | Transition shorthand |
parseCustomStyling({ style, customCSS, customFont }) | Custom CSS overrides |
State-based styling
Interactive elements (inputs, buttons) support state styles (hover, focus, active, disabled, etc.). These are configured per-state in the creative data and applied via:
- Real pseudo-classes for runtime:
${selector}:hover,${selector}:focus - State preview classes for builder:
${selector}.state-hover,${selector}.state-focus
Both are defined in styles() and point to the same style object.
Animation styles
Animation styles from the block's animation config are integrated via:
this.animationStyleProps— applied to the block elementthis.animationHoverStyle— applied to&:hoverthis.animationKeyframeRule—@keyframesdefinitions
These come from the AnimationMixin and are spread into the styles() return object.
Per-input custom styles
Individual form inputs can override global styles. These are computed in perInputCustomStyles and spread into styles():
javascript
return {
...this.perInputCustomStyles,
...this.visualElementsInsideFormStyles,
...this.animationKeyframeRule,
// ... rest of styles
}