Alert

Displays a callout for user attention.

Examples

Basic

PropDefaultTypeDescription
title-stringTitle of the alert
description-stringDescription of the alert
Preview
Code

Variant

PropDefaultTypeDescription
alerttext{variant}The variant of the alert.
VariantDescription
textThe text variant.
solidThe solid variant.
outlineThe outline variant.
softThe soft variant.
borderThe border variant.
Preview
Code

Color

PropDefaultTypeDescription
alert{variant}-gray{variant}-{color}The color of the alert.
Preview
Code

Icon

PropDefaultTypeDescription
icon-stringCustomize the icon of the alert.
Preview
Code

Size

PropDefaultTypeDescription
sizesmstringAllows you to change the size of the input.

🚀 Adjust input size freely using any size, breakpoints (e.g., sm:sm, xs:lg), or states (e.g., hover:lg, focus:3xl).

Preview
Code

Closable

PropDefaultTypeDescription
closable-booleanAdd a close button to the alert.
Preview
Code

Events

Event NameDescription
@closeemit an event when the close icon is clicked. Use in conjunction with closable.

Slots

NamePropsDescription
default-Cover the title and description slots.
title-The title of the alert.
description-The description of the alert.
icon-The icon of the alert.
actions-The actions of the alert.
close-The close icon of the alert.

Example 1

Preview
Code

Example 2

Preview
Code

Example 3

Preview
Code

Presets

shortcuts/alert.ts
type AlertPrefix = 'alert'

export const staticAlert: Record<`${AlertPrefix}-${string}` | AlertPrefix, string> = {
  // config
  'alert-info-icon': 'i-lucide-info',
  'alert-error-icon': 'i-lucide-circle-alert',
  'alert-success-icon': 'i-lucide-circle-check',
  'alert-warning-icon': 'i-lucide-triangle-alert',
  'alert-close-icon': 'i-lucide-x',

  // base
  'alert': 'rounded-lg relative grid grid-cols-[0_1fr] w-full items-start gap-y-0.14285714285714285em px-4 py-3 text-0.875em leading-1.4285714285714286em has-[>span[icon-base]]:grid-cols-[calc(var(--spacing)*4)_1fr] [&>span[icon-base]]:translate-y-0.5 has-[>span[icon-base]]:gap-x-0.8571428571428571em [&>span[icon-base]]:text-current [&>span[icon-base]]:square-1.1428571428571428em',
  'alert-title': 'col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight',
  'alert-description': 'text-muted-foreground col-start-2 grid justify-items-start gap-0.017857142857142856em text-0.875em leading-1.4285714285714286em [&_p]:leading-relaxed',
  'alert-close-wrapper': 'absolute right-3 top-3',

  // static variants
  'alert-solid-primary': ' bg-primary text-primary-foreground [&_[data-slot="alert-description"]]:text-primary-foreground/90',
  'alert-solid-gray': ' bg-secondary border text-secondary-foreground [&_[data-slot="alert-description"]]:text-secondary-foreground/90',
  'alert-solid-white': ' border text-foreground bg-background [&_[data-slot="alert-description"]]:text-foreground',
  'alert-solid-black': ' text-background bg-foreground [&_[data-slot="alert-description"]]:text-background',

  'alert-soft-gray': ' border bg-card text-card-foreground [&_[data-slot="alert-description"]]:text-card-foreground/90',
  'alert-soft-primary': ' bg-primary/10 text-primary [&_[data-slot="alert-description"]]:text-primary/90',

  'alert-outline-gray': ' bg-background border text-card-foreground [&_[data-slot="alert-description"]]:text-card-foreground/90',
  'alert-outline-primary': 'bg-background border border-primary/90 text-primary [&_[data-slot="alert-description"]]:text-primary/90',

  'alert-text-gray': ' border bg-card text-card-foreground',
  'alert-text-primary': 'border border-border bg-card text-primary [&_[data-slot="alert-description"]]:text-primary/90',

  'alert-border-gray': 'border-l-4 rounded-none bg-card text-card-foreground [&_[data-slot="alert-description"]]:text-card-foreground/90',
  'alert-border-primary': 'border-l-4 rounded-0 border-primary/90 bg-primary/10 text-primary [&_[data-slot="alert-description"]]:text-primary/90',
}

export const dynamicAlert: [RegExp, (params: RegExpExecArray) => string][] = [
  // dynamic variants
  [/^alert-border(-(\S+))?$/, ([, , c = 'primary']) => `rounded-none border-l-4 border-${c}-600 dark:border-${c}-500 bg-${c}-50 dark:bg-${c}-950 text-${c}-900 dark:text-${c}-100 [&_[data-slot="alert-description"]]:text-${c}-900/90 dark:[&_[data-slot="alert-description"]]:text-${c}-100/90`],
  [/^alert-solid(-(\S+))?$/, ([, , c = 'primary']) => ` bg-${c}-600 dark:bg-${c}-500 text-background [&_[data-slot="alert-description"]]:text-background/90`],
  [/^alert-outline(-(\S+))?$/, ([, , c = 'primary']) => ` bg-background border border-${c}-600 dark:border-${c}-500 text-${c}-600 dark:text-${c}-500 [&_[data-slot="alert-description"]]:text-${c}-600/90 dark:[&_[data-slot="alert-description"]]:text-${c}-500/90`],
  [/^alert-soft(-(\S+))?$/, ([, , c = 'primary']) => ` border-${c}-50 bg-${c}-50 text-${c}-900 dark:border-${c}-950 dark:bg-${c}-950 dark:text-${c}-100 [&_[data-slot="alert-description"]]:text-${c}-900/90 dark:[&_[data-slot="alert-description"]]:text-${c}-100/90`],
  [/^alert-text(-(\S+))?$/, ([, , c = 'primary']) => ` border bg-card text-${c}-600 dark:text-${c}-500 [&_[data-slot="alert-description"]]:text-${c}-600/90 dark:[&_[data-slot="alert-description"]]:text-${c}-500/90`],
]

export const alert = [
  ...dynamicAlert,
  staticAlert,
]

Props

types/alert.ts
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'

export interface NAlertProps {
  size?: HTMLAttributes['class']
  class?: HTMLAttributes['class']
  /**
   * Allows you to add `UnaUI` alert preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/alert.ts
   * @example
   * alert="outline-pink"
   */
  alert?: HTMLAttributes['class']
  /**
   * Add icon to the alert,
   * If this is true, the icon will be automatically generated based on the color.
   * Supported colors are `info`, `success`, `warning`, and `error`
   *
   * You can customize the icon by providing the icon that you want.
   *
   * @example
   * icon="i-heroicons-information-circle"
   */
  icon?: string | boolean
  /**
   * Add a close button to the alert.
   *
   * @default false
   */
  closable?: boolean
  /**
   * Add a title to the alert.
   */
  title?: string
  /**
   * Add a description to the alert.
   */
  description?: string

  // subcomponents
  _alertTitle?: NAlertTitleProps
  _alertDescription?: NAlertDescriptionProps
  _alertClose?: NAlertCloseProps

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/alert.ts
   */
  una?: NAlertUnaProps
}

export interface NAlertTitleProps {
  size?: HTMLAttributes['class']
  class?: HTMLAttributes['class']
  una?: Pick<NAlertUnaProps, 'alertTitle'>
}

export interface NAlertDescriptionProps {
  size?: HTMLAttributes['class']
  class?: HTMLAttributes['class']
  una?: Pick<NAlertUnaProps, 'alertDescription'>
}

export interface NAlertCloseProps extends Omit<NButtonProps, 'una'> {
  size?: HTMLAttributes['class']
  class?: HTMLAttributes['class']
  una?: Pick<NAlertUnaProps, 'alertCloseWrapper'> & NButtonProps['una']
}

export interface NAlertUnaProps {
  alert?: HTMLAttributes['class']
  alertTitle?: HTMLAttributes['class']
  alertDescription?: HTMLAttributes['class']
  alertCloseWrapper?: HTMLAttributes['class']
}

Components

Alert.vue
AlertTitle.vue
AlertDescription.vue
AlertClose.vue
<script setup lang="ts">
import type { NAlertProps } from '../../types'
import { computed } from 'vue'
import { cn } from '../../utils'
import Icon from '../elements/Icon.vue'
import AlertClose from './AlertClose.vue'
import AlertDescription from './AlertDescription.vue'
import AlertTitle from './AlertTitle.vue'

const props = withDefaults(defineProps<NAlertProps>(), {
  alert: 'text-gray',
  icon: false,
  closable: false,
  size: 'sm',
})

const emit = defineEmits<{
  (e: 'close'): void
}>()

const alertClassVariants = computed(() => {
  const icon = {
    info: 'alert-info-icon',
    success: 'alert-success-icon',
    warning: 'alert-warning-icon',
    error: 'alert-error-icon',
    default: '',
  }

  // TODO: simplify and optimize this
  const alertType = props.alert ? (props.alert.includes('info') ? 'info' : (props.alert.includes('success') ? 'success' : (props.alert.includes('warning') ? 'warning' : (props.alert.includes('error') ? 'error' : 'default')))) : 'default'

  return {
    icon: icon[alertType],
  }
})

const icon = computed(() => {
  if (props.icon === '' || props.icon === undefined || props.icon === true)
    return alertClassVariants.value.icon

  return props.icon.toString()
})
</script>

<template>
  <div
    data-slot="alert"
    role="alert"
    :size
    :class="cn(
      'alert',
      props.una?.alert,
      props.class,
    )"
    :alert
  >
    <slot>
      <slot v-if="$slots.icon || props.icon !== false" name="icon">
        <Icon :name="icon" />
      </slot>

      <AlertTitle
        v-if="title || $slots.title"
        :size
        :una
        v-bind="props._alertTitle"
      >
        <slot name="title">
          {{ title }}
        </slot>
      </AlertTitle>

      <AlertDescription
        v-if="description || $slots.description"
        :size
        :una
        v-bind="props._alertDescription"
      >
        <slot name="description">
          {{ description }}
        </slot>
      </AlertDescription>

      <AlertClose
        v-if="$slots.close || props.closable"
        :size
        v-bind="props._alertClose"
        :una
        @click="emit('close')"
      >
        <slot name="close" />
      </AlertClose>

      <slot v-if="$slots.actions" name="actions" />
    </slot>
  </div>
</template>