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>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" />
  <div class="flex items-center gap-2">
    <Checkbox id="terms" v-model:checked="checked" />
    <Label for="terms">Accept terms</Label>
  </div>
  <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 { success, error } = useToast()
const isSubmitting = ref(false)

async function handleSubmit() {
  isSubmitting.value = true
  try {
    await submitForm()
    success('Submitted successfully')
  } catch (err) {
    error('Submission failed')
  } 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 { success } = useToast()

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

// Debounce hook (returns object with debounced function)
const { debounced: debouncedSearch } = useDebounce(async (query: string) => {
  const results = await searchApi(query)
  // Handle results
}, 300)

async function handleCopy(text: string) {
  await copy(text)
  success('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.