Styling & Tailwind
VastX Native uses Tailwind v3 + the bundled @nativescript/tailwind adapter. The generated PostCSS config runs the framework's compatibility wrapper after Tailwind — it converts 1rem to 16 native DIPs and preserves max dimensions and text properties the upstream adapter would otherwise drop. The upstream adapter is pinned and installed as a framework dependency; don't add it directly to your app or activate its separate Tailwind v4 autoloader.
Read STYLING.md (generated into every new project) for the spacing contract — new projects also receive an AGENTS.md so AI coding agents use the same conventions. init, dev, and build migrate an old, untouched generated PostCSS config with a backup; custom configurations are preserved with migration instructions.
Why not the full Tailwind utility set?
NativeScript's CSS engine only understands a fixed set of properties — spacing, color, typography, borders/radius, opacity, shadow, visibility, z-index, and flex properties (on a real <FlexboxLayout> only). It has no concept of display, position, overflow, or CSS grid at all — those are NativeScript layout containers (StackLayout/FlexboxLayout/GridLayout/AbsoluteLayout), never CSS properties.
So create generates a tailwind.config.cjs with corePlugins as an allowlist of only the utilities that map to something NativeScript can actually render — running Tailwind's full, unfiltered utility set would just generate a lot of classes that silently do nothing. It's pinned to Tailwind v3 specifically, since v4 dropped the JS-config corePlugins API this relies on.
<template>
<NPage title="Sign in">
<NFlex class="flex-col p-5">
<NText text="Welcome back" variant="h1" align="center" class="mb-4" />
<NFlex class="flex-row items-center justify-between bg-slate-100 rounded-lg p-3 mb-4">
<NText text="Remember me" />
<NSwitch v-model="remember" />
</NFlex>
<NButton text="Sign in" size="lg" @tap="signIn" />
</NFlex>
</NPage>
</template>
<NFlex> is a bare <FlexboxLayout> wrapper — flex utility classes (flex-row/flex-col, items-*, justify-*) only do anything on one of these.
Not supported: gap-* — confirmed on a real Android device that a non-zero row-gap on a <FlexboxLayout> silently drops its last child from the native view tree entirely (a real bug in NativeScript's own compiled org.nativescript.widgets.FlexboxLayout widget, not this framework's translation of it). It's disabled outright rather than left as a trap — gap-* classes aren't even generated. Use a margin utility on every child but the last instead (mb-4 in a column, mr-4 in a row), as in the example above. grid-* (NativeScript's grid is configured via columns/rows template strings, not CSS), absolute/inset-*/top-* (no CSS position concept), and Tailwind's rotate-*/scale-*/translate-*/transform utilities (they compose via CSS custom properties NativeScript's transform parser doesn't resolve — use the native rotate/scaleX/translateX style props directly instead, or the typed transform() helper) are also not supported.
Verified end to end at the pipeline level (real Tailwind + PostCSS output fed through the same CSS parser @nativescript/webpack uses) — this ships Android-first, with iOS's <FlexboxLayout> performance deliberately left to a real on-device benchmark before it's the default there too.
Native element attributes: camelCase, not kebab-case
nativescript-vue's renderer does not camelize kebab-case template attributes for native elements (<Page>, <Label>, <Switch>, <TextField>, <Image>, <ScrollView>, ...) the way Vue's DOM renderer does for real HTML — an attribute name is passed completely literally to the native view. is-enabled="false" silently sets a nonsense nativeView['is-enabled'] property that nothing reads, instead of nativeView.isEnabled; the binding no-ops with no warning. This does not affect this framework's own <N*> components (<NPage title="...">, <NButton disabled>, ...) — Vue's real component-prop resolution handles those correctly regardless of casing, since that's a different code path from raw native-element rendering.
So when binding directly onto a native element (rather than one of this framework's own components), always use camelCase: isEnabled, textWrap, maxLength, verticalAlignment, offBackgroundColor, and so on — never is-enabled, text-wrap, etc. This project's own ESLint config enforces this (vue/attribute-hyphenation is set to 'never', the opposite of Vue's own default) precisely because the default assumption — kebab-case is the safe, idiomatic choice — is backwards here and would auto-"fix" correct code back into a silent bug.