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's Container: a single real child (ContentView), decorated with the same box()/padding()/margin()/size() style helpers as props instead of a :style object.
  • <NetworkImage> / <CachedImage> — <Image src="https://..."> already loads remote URLs natively; these wrap it with a real loading spinner and a #fallback slot for failed loads. CachedImage forces useCache: true; NetworkImage defaults to false (always re-fetches) — that distinction is genuinely Android-only, since useCache doesn't exist on NativeScript's iOS Image at all.
  • <NSkeleton> — a loading placeholder with a real animated shimmer sweep, driven by NativeScript's own View.animate() (not CSS @keyframes), looping via iterations: 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.

FunctionCovers
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