STX Components
Stacks.js includes a broad component set built with STX and styled with Crosswind. Treat component count, tree-shaking, accessibility, and dark-mode behavior as version-specific properties to verify against the selected package and application build.
Protocol context — This guide covers the Stacks.js View implementation. The draft Model–View–Action architecture defines portable responsibilities; STX and Crosswind remain TypeScript/Bun-specific choices.
Form Input Components
TextInput
Versatile text input with icons, clear button, and character counter:
<x-text-input
label="Username"
placeholder="Enter your username"
:value="username"
="username = $event"
left-icon="user"
clearable
:max-length="20"
show-count
/>
<x-text-input
label="Email"
type="email"
:error="errors.email"
helper-text="We'll never share your email"
required
/>
Props: value, placeholder, disabled, readonly, size (sm/md/lg), error, label, helperText, required, leftIcon, rightIcon, clearable, maxLength, showCount
EmailInput
Email input with built-in validation:
<x-email-input
label="Email Address"
:value="email"
="email = $event"
required
/>
PasswordInput
Password input with visibility toggle and strength indicator:
<x-password-input
label="Password"
:value="password"
="password = $event"
show-strength
required
/>
Props: showStrength enables the password strength indicator
NumberInput
Number input with increment/decrement controls:
<x-number-input
label="Quantity"
:value="quantity"
="quantity = $event"
:min="1"
:max="100"
:step="1"
show-controls
/>
SearchInput
Search input with debounced callback and loading state:
<x-search-input
placeholder="Search products..."
:value="query"
="handleSearch"
:loading="isSearching"
/>
Textarea
Multi-line input with auto-resize:
<x-textarea
label="Description"
:value="description"
="description = $event"
:rows="4"
auto-resize
:max-rows="10"
:max-length="500"
show-count
/>
Checkbox
Checkbox with indeterminate state support:
<x-checkbox
:checked="agreeToTerms"
="agreeToTerms = $event"
label="I agree to the terms and conditions"
description="By checking this box, you agree to our Terms of Service"
/>
<!-- Indeterminate state for "select all" -->
<x-checkbox
:checked="allSelected"
:indeterminate="someSelected"
="toggleAll"
label="Select All"
/>
Radio
Radio button with description:
<x-radio
:checked="plan === 'pro'"
="plan = 'pro'"
value="pro"
name="plan"
label="Pro Plan"
description="$29/month - Unlimited projects"
/>
Select
Native-style select dropdown:
<x-select
label="Country"
:value="country"
="country = $event"
:options="countries"
placeholder="Select a country"
required
/>
Buttons & Actions
Button
Versatile button with multiple variants:
<!-- Variants -->
<x-buttonvariant="Primary</x-button
<x-buttonvariant="Secondary</x-button
<x-buttonvariant="Outline</x-button
<x-buttonvariant="Ghost</x-button
<x-buttonvariant="Danger</x-button
<!-- Sizes -->
<x-buttonsize="Extra Small</x-button
<x-buttonsize="Small</x-button
<x-buttonsize="Medium</x-button
<x-buttonsize="Large</x-button
<x-buttonsize="Extra Large</x-button
<!-- States -->
<x-button:loading="isSubmittingSubmit</x-button
<x-button:disabled="!isValidSave</x-button
<!-- With icons -->
<x-buttonleft-icon="Add Item</x-button
<x-buttonright-icon="Continue</x-button
<!-- Full width -->
<x-buttonfull-widthFull Width Button</x-button
Layout Components
Card
Content card with image support:
<x-cardvariant="hoverclickable
<x-card-imagesrc="alt="
<x-card-header
<h3Product Name</h3
<pclass="$99.00</p
</x-card-header
<x-card-content
<pProduct description goes here.</p
</x-card-content
<x-card-footer
<x-buttonAdd to Cart</x-button
</x-card-footer
</x-card
Variants: default, outlined, elevated, flat
Accordion
Expandable content sections:
<x-accordion:items="faqItemsallow-multiple
@foreach(faqItemsasitem
<x-accordion-item:key="item
<x-accordion-trigger{{ item.question }}</x-accordion-trigger
<x-accordion-content{{ item.answer }}</x-accordion-content
</x-accordion-item
</x-accordion
Tabs
Tabbed content interface:
<x-tabs:default-tab="'overview'variant="
<x-tab-list
<x-tabvalue="Overview</x-tab
<x-tabvalue="Features</x-tab
<x-tabvalue="Pricing</x-tab
</x-tab-list
<x-tab-panels
<x-tab-panelvalue="
Overview content here...
</x-tab-panel
<x-tab-panelvalue="
Features content here...
</x-tab-panel
<x-tab-panelvalue="
Pricing content here...
</x-tab-panel
</x-tab-panels
</x-tabs
Variants: line, pills, enclosed
Breadcrumb
Navigation breadcrumbs:
<x-breadcrumb:items="breadcrumbs:max-items="4
<template>
<spanclass="/</span
</template
</x-breadcrumb
Stepper
Multi-step progress indicator:
<x-stepper:current-step="currentSteporientation="
<x-step:step="1title="description="
<x-step:step="2title="description="
<x-step:step="3title="description="
</x-stepper
Table
Responsive table component:
<x-tablestripedhoverable
<x-table-head
<x-table-row
<x-table-headerName</x-table-header
<x-table-headerEmail</x-table-header
<x-table-headerRole</x-table-header
</x-table-row
</x-table-head
<x-table-body
@foreach(usersasuser
<x-table-row:key="userhoverable
<x-table-cell{{ user.name }}</x-table-cell
<x-table-cell{{ user.email }}</x-table-cell
<x-table-cell
<x-badge{{ user.role }}</x-badge
</x-table-cell
</x-table-row
</x-table-body
</x-table
Dropdown & Selection
Dropdown
Menu dropdown component:
<x-dropdown
<x-dropdown-button
Options
</x-dropdown-button
<x-dropdown-items
<x-dropdown-item@click="editEdit</x-dropdown-item
<x-dropdown-item@click="duplicateDuplicate</x-dropdown-item
<x-dropdown-divider
<x-dropdown-item@click="deleteclass="Delete</x-dropdown-item
</x-dropdown-items
</x-dropdown
Listbox
Custom select with keyboard support:
<x-listbox:value="selected@change="selected=event
<x-listbox-labelAssign to</x-listbox-label
<x-listbox-button{{ selected.name }}</x-listbox-button
<x-listbox-options
@foreach(peopleasperson
<x-listbox-option:key="person:value="person
{{ person.name }}
</x-listbox-option
</x-listbox-options
</x-listbox
Combobox
Searchable select with filtering:
<x-combobox:value="selected@change="selected=event
<x-combobox-input
:display-value="(item) => item?.name"
="query = $event.target.value"
/>
<x-combobox-button
<x-combobox-options
@foreach(filteredItemsasitem
<x-combobox-option:key="item:value="item
{{ item.name }}
</x-combobox-option
</x-combobox-options
</x-combobox
RadioGroup
Radio button group:
<x-radio-group:value="plan@change="plan=event
<x-radio-group-labelSelect a plan</x-radio-group-label
@foreach(plansasp
<x-radio-group-option:key="p:value="p
<x-radio-group-label{{ p.name }}</x-radio-group-label
<x-radio-group-description{{ p.description }}</x-radio-group-description
</x-radio-group-option
</x-radio-group
Display & Feedback
Badge
Labels and tags:
<x-badgeDefault</x-badge
<x-badgevariant="Primary</x-badge
<x-badgevariant="Success</x-badge
<x-badgevariant="Warning</x-badge
<x-badgevariant="Danger</x-badge
<!-- With dot indicator -->
<x-badgevariant="dotActive</x-badge
<!-- Removable -->
<x-badgeremovable@remove="tag{{ tag }}</x-badge
Avatar
User avatars with fallback:
<!-- With image -->
<x-avatarsrc="alt="size="
<!-- With initials fallback -->
<x-avatarinitials="size="
<!-- With status indicator -->
<x-avatarsrc="status="
<!-- Square shape -->
<x-avatarsrc="shape="
Sizes: xs, sm, md, lg, xl, 2xl
Status: online, offline, away, busy
Spinner
Loading spinners:
<x-spinner
<x-spinnervariant="
<x-spinnervariant="
<x-spinnervariant="
<!-- With label -->
<x-spinnerlabel="
<!-- Custom color -->
<x-spinnercolor="
Skeleton
Loading placeholders:
<x-skeletonvariant="
<x-skeletonvariant="
<x-skeletonvariant="
<x-skeletonvariant="
<x-skeletonvariant="
<x-skeletonvariant="
<!-- Multiple lines -->
<x-skeletonvariant=":count="3
<!-- Custom size -->
<x-skeletonwidth="height="
Progress
Progress indicators:
<!-- Linear -->
<x-progress:value="75:max="100show-label
<!-- Circular -->
<x-progress:value="75variant="size="
<!-- Indeterminate -->
<x-progressindeterminate
Notification
Toast notifications:
<x-notification
:show="showNotification"
title="Success!"
message="Your changes have been saved."
type="success"
position="top-right"
:duration="5000"
="showNotification = false"
/>
Types: info, success, warning, error
Positions: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right
Tooltip
Contextual tooltips:
<x-tooltipcontent="position="
<x-buttonHover me</x-button
</x-tooltip
Overlay Components
Dialog
Modal dialog:
<x-dialog:open="isOpen@close="isOpen=false
<x-dialog-backdrop
<x-dialog-panel
<x-dialog-titleConfirm Action</x-dialog-title
<x-dialog-description
Are you sure you want to delete this item?
</x-dialog-description
<divclass="
<x-buttonvariant="@click="confirmDeleteDelete</x-button
<x-buttonvariant="@click="isOpen=falseCancel</x-button
</div
</x-dialog-panel
</x-dialog
Drawer
Slide-out panel:
<x-drawer:open="isOpen@close="isOpen=falseposition="title="
<divclass="
<!-- Drawer content -->
</div
</x-drawer
Positions: right, left, top, bottom
Popover
Floating panel:
<x-popover
<x-popover-button
<x-buttonOpen Popover</x-button
</x-popover-button
<x-popover-panelposition="
<divclass="
Popover content here...
</div
</x-popover-panel
</x-popover
Media Components
Image
Optimized images with lazy loading:
<x-image
src="/photo.jpg"
webp-src="/photo.webp"
alt="Description"
:width="800"
:height="600"
lazy
placeholder="blur"
:blur-data-url="blurDataUrl"
aspect-ratio="16/9"
object-fit="cover"
rounded
zoom-on-hover
/>
Video
Video player:
<x-video
src="/video.mp4"
poster="/poster.jpg"
controls
:autoplay="false"
:loop="false"
aspect-ratio="16/9"
/>
Audio
Audio player:
<x-audio
src="/audio.mp3"
controls
show-waveform
title="Podcast Episode 1"
/>
Advanced Components
VirtualList
Windowed list for large datasets:
<x-virtual-list
:items="largeDataset"
:item-height="50"
:height="400"
:overscan="5"
>
<template>
<divclass="
{{ item.name }}
</div
</template
</x-virtual-list
VirtualTable
Windowed table for large datasets:
<x-virtual-table
:columns="columns"
:data="largeDataset"
:row-height="48"
:height="600"
striped
hoverable
/>
CommandPalette
Searchable command menu (Cmd+K style):
<x-command-palette
:open="isOpen"
="isOpen = false"
:query="searchQuery"
-change="searchQuery = $event"
>
<x-command-groupheading="
<x-command-item@select="createNewCreate New...</x-command-item
<x-command-item@select="searchSearch...</x-command-item
</x-command-group
<x-command-groupheading="
<x-command-item@select="goToDashboardDashboard</x-command-item
<x-command-item@select="goToSettingsSettings</x-command-item
</x-command-group
</x-command-palette
Business Components
Form
Form wrapper with validation:
<x-form
:validation-schema="schema"
:initial-values="formData"
="handleSubmit"
="handleError"
>
<x-text-inputname="label="required
<x-email-inputname="label="required
<x-textareaname="label="
<x-buttontype="Submit</x-button
</x-form
Auth Components
Pre-built authentication forms:
<!-- Login -->
<x-login
show-logo
show-social-login
show-remember-me
show-forgot-password
show-signup
="handleLogin"
/>
<!-- Signup -->
<x-signup
show-social-signup
="handleSignup"
/>
<!-- Two-Factor Challenge -->
<x-two-factor-challenge
:code-length="6"
="handleTwoFactor"
-recovery-code="showRecoveryForm = true"
/>
Payment Components
Stripe integration components:
<!-- Checkout -->
<x-checkout
:products="cartItems"
:stripe-public-key="stripeKey"
api-url="/api/checkout"
="handleCheckout"
-product="removeFromCart"
shipping
taxes
/>
<!-- Subscription Checkout -->
<x-subscription-checkout
:products="subscriptionPlans"
:stripe-public-key="stripeKey"
api-url="/api/subscribe"
/>
<!-- Payment Methods -->
<x-payment-methods
:stripe-public-key="stripeKey"
api-url="/api/payment-methods"
/>
Transition Component
Animation wrapper for enter/leave transitions:
<x-transition
:show="isVisible"
preset="fade"
:duration="300"
>
<divContent to animate</div
</x-transition
<!-- Custom transitions -->
<x-transition
:show="isVisible"
enter="transition-opacity duration-300"
enter-from="opacity-0"
enter-to="opacity-100"
leave="transition-opacity duration-300"
leave-from="opacity-100"
leave-to="opacity-0"
>
<divCustom animated content</div
</x-transition
Presets: fade, slide, slideLeft, slideRight, scale, rotate, zoom
Component Theming
All components support theming via Crosswind:
// crosswind.config.ts
export default {
theme: {
extend: {
components: {
button: {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800',
},
input: {
base: 'border border-gray-300 rounded-lg',
focus: 'ring-2 ring-blue-500',
error: 'border-red-500',
},
},
},
},
}