UI Kit
<template>
<NPage title="Sign in">
<StackLayout style="padding: 20">
<NText text="Welcome back" variant="h1" />
<NInput v-model="email" label="Email" placeholder="you@example.com" />
<NInput v-model="password" label="Password" secure :error="error" />
<NButton text="Sign in" variant="primary" :loading="loading" @tap="signIn" />
<NButton text="Forgot password?" variant="ghost" @tap="openHelp" />
</StackLayout>
</NPage>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useBottomSheet } from 'vastx-native/runtime/composables/useBottomSheet.js'
import HelpSheet from '../components/help-sheet.vue'
const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)
const { open } = useBottomSheet()
async function signIn() {
loading.value = true
// ...
loading.value = false
}
function openHelp() {
open(HelpSheet)
}
</script>
Every component ships with sensible defaults pulled from a shared theme.ts (colors, spacing, radii, type scale) — pass a variant/size prop to restyle, or override individual style props directly, same as any NativeScript-Vue element.
<NPage> infers whether to show its native ActionBar from whether you passed a title or an actionBar slot — pass hideActionBar explicitly (:hideActionBar="true") when you're building a fully custom in-content header instead, rather than relying on that inference.
The kit covers <NButton>, <NText>, <NInput>, <NCard>, <NSwitch>, <NSpinner>, <NAvatar>, <NBadge>, <NDivider>, <NFlex>, <NContainer>, <NetworkImage>, <CachedImage>, <NSkeleton>, plus useBottomSheet()/useModal() for showing any component of your own as a bottom sheet or modal via nativescript-vue's real $showModal.
Container, images, and loading placeholders
<template>
<NContainer :padding="'lg'" :background="'surface'" :radius="'md'" :elevated="true">
<CachedImage :src="product.imageUrl" :width="120" :height="120" :radius="'md'">
<template #fallback>
<NSkeleton :width="120" :height="120" radius="md" />
</template>
</CachedImage>
</NContainer>
</template>
<NContainer>— Flutter'sContainer: a single real child (ContentView), decorated with the samebox()/padding()/margin()/size()style helpers as props instead of a:styleobject.<NetworkImage>/<CachedImage>—<Image src="https://...">already loads remote URLs natively; these wrap it with a real loading spinner and a#fallbackslot for failed loads.CachedImageforcesuseCache: true;NetworkImagedefaults tofalse(always re-fetches) — that distinction is genuinely Android-only, sinceuseCachedoesn't exist on NativeScript's iOSImageat all.<NSkeleton>— a loading placeholder with a real animated shimmer sweep, driven by NativeScript's ownView.animate()(not CSS@keyframes), looping viaiterations: Number.POSITIVE_INFINITY, stopped cleanly on unmount.
Typed style helpers
An alternative to Tailwind classes — Flutter's actual mechanism (widgets take typed style objects, not a CSS engine), which NativeScript already supports directly: every :style binding is already a plain JS object, not a CSS string. vastx-native/runtime/style.js gives that a small, typed API built on the same tokens the UI kit uses.
All of these are auto-imported — no import statement needed. Combine them by binding :style to an array (Vue's own built-in style-array merging):
<template>
<NFlex :style="[padding('lg'), bg('surface'), flex({ direction: 'column' })]">
<Label text="Total" :style="textStyle({ size: 'xl', weight: 'bold', color: 'primary' })" />
<Label text="Card" :style="[border({ radius: 'md' }), box({ elevated: true })]" />
</NFlex>
</template>
Lengths (padding, margin, size, border's width/radius, textStyle's size/lineHeight) accept a spacing token ('md'), a raw number (device-independent pixels), or a '16px'/'1rem' string — 1rem is a deliberate convention borrowed from CSS (= 16dip), not a real NativeScript unit.
| Function | Covers |
|---|---|
padding(length | sides) / margin(length | sides) | Box model spacing, per-side via { top, right, bottom, left, horizontal, vertical, all } |
size({ width, height, minWidth, minHeight, maxWidth, maxHeight }) | Dimensions — '100%'/'auto' pass through literally |
bg(color) / bg({ color, image, repeat, position, size }) | Background color, or the full shorthand (image supports real linear-gradient(...)) |
box({ radius, background, elevated, borderColor, borderWidth, opacity, clipPath }) | The common-case container look — single radius/border, optional shadow |
border({ width, color, radius, cornerShape }) | Per-side width/color, per-corner radius, 'round'/'squircle' corners |
textStyle({ size, weight, color, align, family, style, decoration, transform, letterSpacing, lineHeight, whiteSpace, overflow, maxLines, shadow, stroke, variationSettings }) | Full text styling |
flex({ direction, wrap, justify, align, alignContent, gap, rowGap, columnGap, grow, shrink, alignSelf, order, wrapBefore }) | Flex container/item properties — only takes effect on a real <NFlex>/<FlexboxLayout>. Avoid gap/rowGap/columnGap — confirmed on a real Android device that a non-zero row-gap silently drops a <FlexboxLayout>'s last child from the native view tree entirely (see Styling & Tailwind); use per-child margin instead |
transform({ rotate, rotateX, rotateY, scaleX, scaleY, translateX, translateY, perspective }) | Real, literal-number transforms |
align({ horizontal, vertical }) | A view's own position within its parent |
visible(value) / zIndex(value) | 'visible' | 'hidden' | 'collapse'; stacking order |
elevation(value, dynamicOffset?) | Android-only Material elevation shadow |
direction(value) | iOS-only 'ltr' | 'rtl' |
tintColor(color) | A view's inherited tint color |
placeholder(color) | Placeholder text color for NInput |
opacity(color, amount) / shade(color, amount) / tint(color, amount) | Flutter-style color manipulation — real hex/rgb math |