Appearance
CSS Classes from Flow Variables
Summary
Automatically inject CSS classes onto the creative widget based on flow variable state. Users can write conditional CSS targeting these classes — no JavaScript needed.
MVP — Implemented ✅
Classes added to widget root (.creative-outer-container)
| Variable state | CSS class | Example use |
|---|---|---|
answer = "Oslo" | .cavai-answer-oslo | Style based on what user chose |
answer.position = 2 | .cavai-answer-position-2 | Style based on which option |
score = 3 | .cavai-var-score-3 | Style based on custom variable |
Class naming: cavai- prefix to avoid collisions. Values are slugified (lowercase, spaces/special chars → hyphens).
Important: Classes go on .creative-outer-container, NOT #creative-container. The latter has a reactive Vue :class binding that overwrites manually added classes on re-render. .creative-outer-container is set once in Creative.vue mounted() and is stable.
Files changed
CE/src/utils/flowClasses.ts (NEW)
slugify(str)— safe CSS class namesupdateFlowClasses()— removes oldcavai-*classes, adds current state- Targets
.creative-outer-containerwith fallback toDataStore.mainDivID - Only touches
cavai-answer-*andcavai-var-*classes — never removes existing classes
CE/src/style-engine/styleTreeParser/index.ts (MODIFIED)
CSS namespacing fix: .cavai-* selectors are combined with the container selector (.creative-outer-container.cavai-answer-oslo) instead of nested as descendants (.creative-outer-container .cavai-answer-oslo). This is because the classes are ON the container element itself, not on a descendant.
ts
// In prependOuterContainerSelectorConditionally:
if (subSelector.startsWith('.cavai-')) {
return containerSelector + subSelector // Combined: .creative-outer-container.cavai-answer-oslo
}
return [containerSelector, subSelector].join(' ') // Default: .creative-outer-container .selectorSafe for legacy: no existing creatives use .cavai- prefixed CSS classes.
CE/src/components/blocks/functional/SetVariable.ts
Added updateFlowClasses() call after variable assignment.
CE/src/components/conversationflow/MessageHolder.vue
Added updateFlowClasses() call after answer recording in handleInputEntry.
Practical limitation discovered
Per-answer CSS classes (.cavai-answer-oslo) work technically — the class IS on the container — but background/outline on the container aren't visible because child elements have opaque backgrounds. Also, .choice elements become .response elements after the user clicks, so CSS targeting .choice doesn't work post-answer.
This is why Phase 2 pivoted to .cavai-answer-correct/.cavai-answer-wrong classes, which are more practical for the main quiz use case.
How to test
- Rebuild CE
- Create flow: Question → Answers → CSS operator
- CSS operator:
.cavai-answer-correct { background: green !important; } - Mark one answer as correct (✓ toggle), preview, click
- DevTools: inspect
.creative-outer-container→ should havecavai-answer-correctorcavai-answer-wrongclass
Phase 2 — Correct/Wrong ✅ Implemented
What it does
Mark answer operators as "correct" in the builder (✓ toggle). When user picks the correct answer → .cavai-answer-correct on widget root. Wrong answer → .cavai-answer-wrong. Enables quiz styling with zero code.
Files changed
Builder: AnswerOp.vue
- ✓ toggle button:
position: absolute; top: 4px; right: 24px(next to gear icon) toggleCorrect():Vue.set(this.op.properties.body, 'isCorrectAnswer', !current)- Green circle when active, subtle grey when inactive
- Undo/redo watcher added in
OperatorBase.vue
Composer: remapData.ts
Passthrough at end:
typescript
if (body.isCorrectAnswer) {
comp.payload.isCorrectAnswer = body.isCorrectAnswer
}CE: MessageHolder.vue
In handleInputEntry, after recording the answer:
typescript
const hasCorrectMarker = this.componentsWithOverrides.some(c => c.payload?.isCorrectAnswer)
if (hasCorrectMarker) {
const pickedCorrect = this.componentsWithOverrides[meta]?.payload?.isCorrectAnswer === true
DataStore.flowVariables['_answerCorrect'] = pickedCorrect
}CE: flowClasses.ts
typescript
if (vars._answerCorrect !== undefined) {
root.classList.add(vars._answerCorrect ? 'cavai-answer-correct' : 'cavai-answer-wrong')
}Example quiz CSS
css
.cavai-answer-correct { background: #e8f5e9; }
.cavai-answer-wrong { background: #ffebee; }Remaining work for Phase 2
- [ ]
answer.resultvariable: AddDataStore.flowVariables['answer.result'] = 'correct' | 'wrong'— a user-facing string for use in{answer.result}templates and "when answer.result = correct" conditions. Currently only_answerCorrect(boolean, internal) exists. - [ ] Refresh bug:
_answerCorrect(and all flowVariables) persists across preview refresh.resetDataStore()replaces the reactive proxy reference but doesn't clear the old proxy in-place.
Phase 3 — More built-in classes (ideas)
| Class | When added | Use case |
|---|---|---|
.cavai-has-answered | After first answer | Show/hide elements after interaction |
.cavai-step-{n} | On each flow step | Progressive styling per step |
.cavai-answered-{n}-times | Incremented per answer | Unlock content after N interactions |
.cavai-has-{varname} | When variable exists | Style based on variable presence |
.cavai-var-{name}-high/medium/low | Threshold detection | Score ranges without exact values |
Threshold classes
Instead of only .cavai-var-score-3 (exact value), add range classes:
score >= 10 → .cavai-var-score-high
score >= 5 → .cavai-var-score-medium
score >= 0 → .cavai-var-score-lowThresholds could be configurable per variable in the SetVariable operator UI.
Phase 4 — Autocomplete (future)
CSS operator autocomplete
When typing .cavai- in the CSS operator, suggest available class names:
- Infer from answer operators:
.cavai-answer-oslo,.cavai-answer-bergen - Infer from SetVariable operators:
.cavai-var-score-0,.cavai-var-status-active
JS operator autocomplete
Suggest document.querySelector('.cavai-...') patterns.
Out of Scope
- CSS-in-JS reactive system (CSS classes are sufficient)
- Server-side class generation
- Class-based analytics