Skip to content

Component Usage Guide

This guide introduces the best ways to use BrutxUI components to help you write robust, maintainable code.

1. Component Selection Guide

1.1 Basic Interaction Components

ScenarioRecommended ComponentDescription
Trigger actionsButtonSupports variant, size, and loading states.
Text inputInputSupports clearable, password toggle, and character counts.
Multi-line textTextareaAutomatically adjusts height.
Numeric inputNumberInputBuilt-in step buttons and range validation.
Single selectRadioGroupSemantic radio groups.
Multi-selectCheckboxSupports indeterminate states.
Toggle switchSwitchBoolean switches.
Options selectionSelect / ComboboxSelect for fixed options; Combobox for searchable items.

1.2 Composite Form Scenarios

vue
<!-- Inputs with prepended or appended content -->
<template>
  <Input v-model="url" placeholder="Enter URL">
    <template #prepend>
      <span class="px-3 border-r-3 border-brutal bg-brutal-muted flex items-center">
        https://
      </span>
    </template>
    <template #append>
      <Button variant="primary" size="sm" @click="verify">Verify</Button>
    </template>
  </Input>
</template>

1.3 Feedback Components

ScenarioRecommended ComponentDescription
Critical confirmationAlertDialogBlocking prompt; requires explicit user action.
Temporary panelsDialog / SheetClosable modal overlays.
Action feedbackToastAuto-dismissing notifications.
Form validation errorFormMessageInline error message.
Long-running operationsProgress / SpinnerVisual progress indicator.

2. Props Best Practices

2.1 Variants and Sizes

BrutxUI components follow a unified variant + size design pattern:

vue
<template>
  <!-- Button Variants -->
  <Button variant="default">Default</Button>
  <Button variant="primary">Primary</Button>
  <Button variant="secondary">Secondary</Button>
  <Button variant="accent">Accent</Button>
  <Button variant="danger">Danger</Button>
  <Button variant="success">Success</Button>
  <Button variant="outline">Outline</Button>
  <Button variant="ghost">Ghost</Button>
  <Button variant="link">Link</Button>

  <!-- Button Sizes -->
  <Button size="sm">Small</Button>
  <Button size="default">Default</Button>
  <Button size="lg">Large</Button>
  <Button size="xl">Extra Large</Button>
</template>

2.2 Explicit Boolean Props

vue
<template>
  <!-- Recommended: Explicit boolean bindings -->
  <Button :loading="isSubmitting">Submit</Button>
  <Button disabled>Disabled</Button>
  <Input v-model="name" clearable />

  <!-- Avoid: Implicit true (less readable) -->
  <!-- <Button loading>Submit</Button> -->
</template>

2.3 Style Customization via Class Prop

Most BrutxUI components accept a class prop to merge and override default styles:

vue
<template>
  <Button variant="primary" class="w-full mt-4">
    Full Width Button
  </Button>

  <Card class="max-w-md mx-auto">
    <CardHeader>
      <CardTitle>Custom Card</CardTitle>
    </CardHeader>
  </Card>
</template>

2.4 TypeScript Prop Extraction

vue
<script setup lang="ts">
import { type ComponentProps } from 'brutx-ui-vue'
import { Button } from 'brutx-ui-vue'

// Extract Props type from the component definition
type ButtonProps = ComponentProps<typeof Button>

// Use within configuration helpers
function createButtonConfig(): ButtonProps {
  return {
    variant: 'primary',
    size: 'lg',
    loading: false,
  }
}
</script>

3. Event Handling

3.1 Two-way Bindings with v-model

vue
<script setup lang="ts">
import { ref } from 'vue'

const text = ref('')
const checked = ref(false)
const selected = ref('')
</script>

<template>
  <Input v-model="text" placeholder="Enter text" />
  <Checkbox v-model:checked="checked" label="Accept terms" />
  <Select v-model="selected" :options="options" />
</template>

3.2 Listening to Component Events

vue
<template>
  <!-- Clear Event -->
  <Input v-model="query" clearable @clear="handleClear" />

  <!-- Close Event -->
  <Alert variant="info" @close="handleAlertClose">
    <AlertTitle>Info</AlertTitle>
    <AlertDescription>Operation completed successfully.</AlertDescription>
  </Alert>

  <!-- Dialog Control -->
  <Dialog v-model:open="dialogVisible">
    <DialogTrigger as-child>
      <Button>Open Dialog</Button>
    </DialogTrigger>
    <DialogContent @close="handleDialogClose">
      <!-- Content -->
    </DialogContent>
  </Dialog>
</template>

3.3 Form Submission Workflow

vue
<script setup lang="ts">
import { ref } from 'vue'
import { useToast } from 'brutx-ui-vue'

const { toast } = useToast()
const isSubmitting = ref(false)

async function handleSubmit() {
  isSubmitting.value = true
  try {
    await submitForm()
    toast({ title: 'Submitted successfully', variant: 'success' })
  } catch (error) {
    toast({ title: 'Submission failed', variant: 'destructive' })
  } finally {
    isSubmitting.value = false
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <!-- Form contents -->
    <Button type="submit" :loading="isSubmitting">Submit</Button>
  </form>
</template>

4. Slot Usage

4.1 Default Slots

vue
<template>
  <Button>
    <IconDownload class="w-4 h-4" />
    Download File
  </Button>
</template>

4.2 Named Slots

vue
<template>
  <!-- Prepend & Append in Input -->
  <Input v-model="price" placeholder="0.00">
    <template #prepend>
      <span class="px-3 bg-brutal-muted border-r-3 border-brutal flex items-center">
        $
      </span>
    </template>
    <template #append>
      <span class="px-3 bg-brutal-muted border-l-3 border-brutal flex items-center">
        USD
      </span>
    </template>
  </Input>

  <!-- Actions slot in Alert -->
  <Alert variant="warning">
    <AlertTitle>Warning</AlertTitle>
    <AlertDescription>This action cannot be undone.</AlertDescription>
    <template #actions>
      <Button variant="outline" size="sm" @click="dismiss">Dismiss</Button>
      <Button variant="primary" size="sm" @click="confirm">Confirm</Button>
    </template>
  </Alert>
</template>

4.3 Card Composite Slots

vue
<template>
  <Card>
    <CardHeader>
      <CardTitle>User Profile</CardTitle>
      <CardDescription>Manage details here.</CardDescription>
    </CardHeader>
    <CardContent>
      <!-- Main Content -->
    </CardContent>
    <CardFooter class="justify-end gap-2">
      <Button variant="outline">Cancel</Button>
      <Button variant="primary">Save</Button>
    </CardFooter>
  </Card>
</template>

5. Composition Patterns

5.1 Composite Component Layout

BrutxUI composite components (e.g., Accordion, Tabs, Dialog) leverage the Root-Trigger-Content structure:

vue
<template>
  <!-- Accordion -->
  <Accordion type="single" collapsible>
    <AccordionItem value="item-1">
      <AccordionTrigger>Section One</AccordionTrigger>
      <AccordionContent>Content for section one.</AccordionContent>
    </AccordionItem>
    <AccordionItem value="item-2">
      <AccordionTrigger>Section Two</AccordionTrigger>
      <AccordionContent>Content for section two.</AccordionContent>
    </AccordionItem>
  </Accordion>

  <!-- Tabs -->
  <Tabs default-value="account">
    <TabsList>
      <TabsTrigger value="account">Account</TabsTrigger>
      <TabsTrigger value="password">Password</TabsTrigger>
    </TabsList>
    <TabsContent value="account">Account setup details.</TabsContent>
    <TabsContent value="password">Manage password settings.</TabsContent>
  </Tabs>
</template>

5.2 Composable Hook Logic Reusability

vue
<script setup lang="ts">
import { useToast, useClipboard, useDebounce } from 'brutx-ui-vue'

// Toast hook
const { toast } = useToast()

// Clipboard hook
const { copy, isSupported } = useClipboard()

// Debounce hook
const [debouncedSearch, isDebouncing] = useDebounce(async (query: string) => {
  const results = await searchApi(query)
  // Handle results
}, 300)

async function handleCopy(text: string) {
  await copy(text)
  toast({ title: 'Copied to clipboard!' })
}
</script>

5.3 Provide/Inject State Sharing

vue
<!-- Parent Component: Providing layout details -->
<script setup lang="ts">
import { provide } from 'vue'

provide('formLayout', {
  labelWidth: '120px',
  labelPosition: 'right',
})
</script>

<!-- Child Component: Injecting details -->
<script setup lang="ts">
import { inject } from 'vue'

const layout = inject('formLayout', {
  labelWidth: '100px',
  labelPosition: 'left',
})
</script>

5.4 asChild Pattern

BrutxUI components support the asChild prop, which forwards all classes and events to a single child node:

vue
<template>
  <!-- Render custom NuxtLink styled like a BrutxUI button -->
  <Button as-child>
    <NuxtLink to="/dashboard">
      Go to Dashboard
    </NuxtLink>
  </Button>

  <!-- dialog trigger wrapping custom button -->
  <DialogTrigger as-child>
    <Button variant="outline" size="sm">
      Custom Trigger
    </Button>
  </DialogTrigger>
</template>

5.5 Invoking Ref Methods

Certain components expose instance methods that can be called directly:

vue
<script setup lang="ts">
import { ref } from 'vue'
import { Input } from 'brutx-ui-vue'

const inputRef = ref<InstanceType<typeof Input>>()

function focusInput() {
  inputRef.value?.focus()
}

function selectAll() {
  inputRef.value?.select()
}
</script>

<template>
  <Input ref="inputRef" v-model="value" />
  <Button @click="focusInput">Focus</Button>
  <Button @click="selectAll">Select All</Button>
</template>

Brute force builds.