Inline and JSX styles#
The class and style attributes are plain HTML attributes in Janux — there is no className, and no CSS-in-JS runtime. What the framework adds is types.
class#
class takes a string, so every ordinary JavaScript expression works. Conditional looks are conditional strings:
import { bool, component, intent, schema } from 'janux';
export const Toggle = component({
name: 'toggle',
description: 'A pill that switches between two looks.',
state: schema({ on: bool().default(false) }),
intents: {
flip: intent({ description: 'Flip the switch.', run: ({ state }) => (state.on = !state.on) }),
},
view: ({ state, intents }) => (
<button class={state.on ? 'pill pill-on' : 'pill'} onClick={intents.flip}>
{state.on ? 'On' : 'Off'}
</button>
),
});Because the view re-runs when state changes, and the runtime morphs the DOM in place, the class attribute is simply recomputed — there is no classList bookkeeping to write.
For anything longer than a ternary, name the mapping instead of nesting conditionals in the JSX:
const TONE = { idle: 'chip', busy: 'chip chip-busy', failed: 'chip chip-failed' };
// in the view:
<span class={TONE[state.status]}>{state.status}</span>style#
style accepts either CSS text or a typed object:
<div style="color: red; width: 10px" />
<div style={{ color: 'red', width: '10px' }} />The object form is typed with CSSProperties: properties are camelCased (backgroundColor, borderRadius) and their values are checked, so a typo is a compile error rather than a rule the browser silently drops.
Custom properties are allowed alongside them, which is what makes runtime theming type-safe:
<section style={{ backgroundColor: '#fff', '--brand': '#0062ff' }} />When to use which#
Inline styles win exactly once: when the value is something the stylesheet cannot know.
// Good: the number comes from data, not from the design.
<div class="bar" style={{ width: `${percent}%` }} />Everything else belongs in a class. An inline style has the highest specificity short of !important, so a page that reaches for it by habit becomes a page where the stylesheet no longer decides anything. It also cannot express hover, focus, media queries or prefers-color-scheme.
The middle path — and usually the right one — is to let the class do the styling and the inline style carry only the value:
<div class="meter" style={{ '--fill': `${percent}%` }} />.meter::after {
width: var(--fill);
transition: width 200ms ease;
}Now the look lives in CSS, where hover states and media queries are available, and the component contributes one number.