Inline Alert — 4 Severities
Free · MITStatic inline banner covering info, success, warning, and error severities with the correct assertive/polite ARIA role per severity.
- Category
- Core UI
- Framework
- astro
- Version
- 1.0.0
- Peer deps
- none
Install
velocityx add inline-alert
Free and MIT licensed — no account needed. The file lands under src/components/velocity/inline-alert/ by default (override with --dest), and it's yours to edit from the moment it's written.
Demo
View the live demo on the showcase site ↗
Source
Pulled live from the registry — this is the exact file velocityx add inline-alert writes into your project right now.
components/velocity/inline-alert/InlineAlert.astro
---
// VELOCITY COMPONENT · theming
//
// Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
// the Velocity preset. Out of the box they are a neutral indigo — NOT the
// Aidxn brand — so this drops into your project without dragging our look in.
//
// ⚡ NOT USING VELOCITY? Run this once, in your project root:
//
// velocityx theme
//
// It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
// tailwind.config, import the css, then set your whole brand in ONE place:
//
// :root {
// --vx-brand: custom;
// --vx-primary-600: 220 38 38; <- your action colour
// --vx-primary-700: 185 28 28;
// }
//
// ⚠️ Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
// breaks every /opacity variant in the library (bg-primary-600/20 etc).
//
// Prefer not to install anything? Find-and-replace `primary-` with your own
// scale name. Nothing else in this file depends on Velocity.
/**
* InlineAlert — static banner for info/success/warning/error severities.
* Renders as role="alert" for error/warning (assertive) and role="status"
* for info/success (polite), so screen readers announce it appropriately
* without the page author having to think about it.
*
* Usage:
* <InlineAlert severity="warning" title="Check your details">
* Your card expires this month — update it to avoid a lapse.
* </InlineAlert>
*/
export interface Props {
severity?: 'info' | 'success' | 'warning' | 'error';
title?: string;
dismissible?: boolean;
}
const { severity = 'info', title, dismissible = false } = Astro.props;
const VARIANTS = {
info: {
label: 'Info',
box: 'border-primary-500/25 dark:border-primary-300/20 bg-primary-50 dark:bg-primary-500/10',
icon: 'text-primary-600 dark:text-primary-300',
title: 'text-primary-900 dark:text-primary-100',
role: 'status',
path: 'M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z',
},
success: {
label: 'Success',
box: 'border-green-500/25 dark:border-green-400/20 bg-green-50 dark:bg-green-500/10',
icon: 'text-green-600 dark:text-green-400',
title: 'text-green-900 dark:text-green-100',
role: 'status',
path: 'M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
},
warning: {
label: 'Warning',
box: 'border-orange-500/30 dark:border-orange-400/25 bg-orange-50 dark:bg-orange-500/10',
icon: 'text-orange-600 dark:text-orange-400',
title: 'text-orange-900 dark:text-orange-100',
role: 'alert',
path: 'M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z',
},
error: {
label: 'Error',
box: 'border-red-500/30 dark:border-red-400/25 bg-red-50 dark:bg-red-500/10',
icon: 'text-red-600 dark:text-red-400',
title: 'text-red-900 dark:text-red-100',
role: 'alert',
path: 'M12 9v3.75m0 3.75h.008v.008H12v-.008zM21 12a9 9 0 11-18 0 9 9 0 0118 0z',
},
} as const;
const v = VARIANTS[severity];
const heading = title ?? v.label;
---
<div
role={v.role}
aria-label={heading}
class:list={['flex items-start gap-3 rounded-xl border p-4', v.box]}
data-dismissible={dismissible ? 'true' : undefined}
>
<svg aria-hidden="true" class:list={['mt-0.5 h-5 w-5 shrink-0', v.icon]} fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d={v.path} />
</svg>
<div class="min-w-0 flex-1">
<p class:list={['m-0 text-sm font-semibold', v.title]}>{heading}</p>
<div class="mt-0.5 text-sm leading-relaxed text-secondary-700 dark:text-secondary-300 [&_p]:m-0 [&_p+p]:mt-1.5">
<slot />
</div>
</div>
{dismissible && (
<button
type="button"
aria-label="Dismiss"
class="alert-dismiss shrink-0 inline-flex h-6 w-6 items-center justify-center rounded-md text-secondary-400 hover:bg-black/5 dark:hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-600"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
</button>
)}
</div>
{dismissible && (
<script>
document.querySelectorAll('.alert-dismiss').forEach((btn) => {
btn.addEventListener('click', () => {
const alertEl = btn.closest('[data-dismissible]');
alertEl?.remove();
});
});
</script>
)}
Theming
Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.