Styling

Styling

Use any CSS you like. For scoped styles written next to components, @velozjs/css gives you css, styled and the css prop.

Plain CSS works

Veloz renders real elements with real class attributes, so stylesheets, CSS modules and class-based libraries work as is. This site is styled with salve-css and its icons with salve-icons, both through plain classes.

tsx
import 'salve-css';

<button class="btn btn-soft">Save</button>

@velozjs/css

bash
npm install @velozjs/css

Import it once in your client entry. That import also enables the css prop.

main.tsx
import '@velozjs/css';

css

A tagged template that returns a scoped class name. Top-level declarations apply to the class. Nested rules are always scoped: & marks where the class goes, and a rule without & applies to descendants. @media, @supports and @container scope their contents, and @keyframes / @font-face are kept as written.

tsx
import { css } from '@velozjs/css';

const card = css`
  padding: 1.5rem;
  border-radius: 12px;
  &:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.1); }
  .title { font-weight: 600; }            /* .v-xyz .title */
  @media (min-width: 40em) { padding: 2rem; }
`;

<div class={card}>…</div>;

styled

Creates a component with its styles. Function interpolations receive the props; when they read accessors, the class updates reactively.

tsx
import { styled } from '@velozjs/css';

const Button = styled('button')`
  padding: 0.5rem 1rem;
  border-radius: 999px;
  background: ${(p: { tone?: 'danger' }) => (p.tone === 'danger' ? 'crimson' : 'royalblue')};
`;

<Button tone="danger" onClick={remove}>Delete</Button>;

The css prop

An object of styles on any element. Nested selectors and at-rules work, and numbers get px except for unitless properties (opacity, zIndex, lineHeight...). The same applies to style objects.

tsx
<span
  css={{
    color: 'gray',
    '&:hover': { color: 'black' },
    '.icon': { marginInlineEnd: '0.25rem' },
    '@media (min-width: 40em)': { fontSize: '1.25rem' },
  }}
>
  hint
</span>

How it works

  • Class names are hashes of the CSS (v-1x2y3z), so identical styles share one class.
  • Styles go into a constructable stylesheet (adoptedStyleSheets), falling back to one <style data-veloz-css> element. No inline <style> writes, which keeps it CSP-friendly.
  • During SSR, styles are collected and returned by renderToString as css; put them in <style data-veloz-css> and the client won't inject them again.