This is the canonical public docs page for cross-cutting customization concerns. The guide repo calls this topic
cross-cutting; the docs keep the existing /ui-customization/localisation route and redirect /ui-customization/cross-cutting here.Setup; this is the deep version.)
Accessibility (a11y)
What Velt gives you for free, and when you keep it:- Velt’s default UI and its slot components ship with roles, labels, keyboard handling, and focus management.
- Primitives render Velt’s components, so they keep that a11y. Wrapping a primitive in your own UI library is fine: the Velt part stays accessible.
- Wireframes: Velt’s behavior and a11y live in the
Velt…Wireframe.Xslot components: keep using them for anything interactive (buttons, inputs, dropdowns). The custom markup you add around slots is yours to make accessible. - Headless: you own 100% of the a11y: there’s no Velt UI to inherit from.
- Use semantic elements (
<button>,<nav>,<ul>) and addaria-*/role/aria-labelwhere your markup needs them. - Preserve a sensible focus order and visible focus styles (
:focus-visible). Don’toutline: nonewithout a replacement. - Keep interactive behavior in Velt slot components: a custom
<div onClick>in a wireframe doesn’t even run (R4), and a bare div isn’t keyboard-accessible anyway. - Don’t rely on color alone for state (resolved/unread/priority): pair it with an icon/label.
- When you override colors via
--velt-*, keep contrast at WCAG AA (4.5:1 text). This is easy to break with brand palettes: check both light and dark. - Respect
prefers-reduced-motionin any custom transitions you add (e.g. hover-reveal).
i18n / localization
Three levers, in order of power:- Override Velt’s built-in strings / add languages: the strongest lever:
setTranslationstakes{ [language]: { [key]: value } }: use it to reword or translate Velt’s own UI text, not just translate.setLanguageswitches the active language at runtime. - Placeholder props: composer text:
commentPlaceholder,replyPlaceholder,editPlaceholder,editCommentPlaceholder,editReplyPlaceholder(and the slot-levelplaceholderprops). Pass already-translated values. - Your own text: anything in your wireframe markup or headless components is yours: render it through your app’s i18n library.
RTL (right-to-left)
- Set
dir="rtl"on the container that wraps your Velt UI; Velt’s UI inherits direction from the DOM. - In your own CSS overrides and wireframe markup, prefer logical properties:
margin-inline-start,padding-inline,inset-inline: instead of hardleft/right, so they mirror automatically. - Mirror directional glyphs (chevrons, arrows, “reply” icons): flip them with
transform: scaleX(-1)under[dir="rtl"]if they’re your icons. - Check anything absolutely positioned (pins, dialogs, dropdown popovers) and your
position: relativewrappers: verify they anchor on the correct side. - Verify the whole surface in an RTL language; fix mirroring in your CSS, never by hacking Velt internals (R0).
Responsive / mobile
- Use the layout props first (cheapest):
filterPanelLayout="bottomSheet"andfilterOptionLayouton the sidebar,panelOpenModeon notifications, the dialog’s bottom-sheet mode,embedMode/floatingMode/position. SeeComponent config. - Your media queries apply to your wrappers and your wireframe markup. Velt also flips some internal layouts at mobile breakpoints on its own.
- Touch targets ≥ ~44px. Important for any custom buttons you place in wireframe slots.
- Hover-reveal caveat: the “show actions on hover” recipe (
Patterns and tips) has no hover on touch. Velt already force-shows per-comment actions on mobile widths; if you build your own hover-reveal, add a tap/always-visible fallback under a mobile media query. - Test the scroll/height chain (R14) at mobile sizes too: collapsed viewports expose missing
min-height:0links.
Testing / verifying a customization
For the full definition of “done”: driving each surface’s states, confirming Velt’s behavior is intact, the static rules scan, and the PASS/PARTIAL/FAIL/BLOCKED verdict: see
Verifying a customization. The checklist below is the cross-cutting slice of that flow (a11y/dark/RTL/responsive), applied to every surface with custom markup.- States: empty, loading/skeleton, unread, resolved, private, filtered-to-zero, long content (truncation).
- Dark mode on/off; RTL on/off; mobile width.
- Keyboard navigation + visible focus; quick screen-reader pass.
- Scroll actually works (R14); nothing overflows or clips.
- Compare against default: temporarily remove your customization to confirm a problem is yours vs Velt’s.
- Assert on Velt’s stateful classes from
CSS classes: e.g. expect.velt-comment-pin-unread-commentpresent/absent. - Read live data via
Hooksin component tests: e.g. assertuseUnreadCommentAnnotationCountOnCurrentDocument()?.count. - Put
data-testidon your own wrapper/markup (not on Velt internals, whose class/structure can change between versions) and target those in e2e. - Visual regression snapshots per surface × (light/dark) × (LTR/RTL) catch CSS-override drift after SDK upgrades: the main risk for class-based overrides.

