Banner
Banners are persistent inline messages with an optional icon, action, close button and details panel.
import { Banner } from '@aksornproject/aksara-ui'Variants
Banner is a namespace. Banner.Base is the root, and Banner.Icon, Banner.Content, Banner.Title, Banner.Description, Banner.Action and Banner.Panel fill it.
variant sets the fill, the icon colour and the default role. Use information, success, warning and error for a status. default is a neutral grey and brand uses the accent colour, for messages that carry no status.
The Banner never picks a glyph. Pass one to Banner.Icon through icon or children, and it renders in a 24px slot in the status colour.
<div className="flex w-full flex-col gap-spacing-12">
{messages.map(({ variant, icon, title, description }) => (
<Banner.Base key={variant} variant={variant} showCloseButton={false}>
<Banner.Icon icon={icon} />
<Banner.Content>
<Banner.Title>{title}</Banner.Title>
<Banner.Description>{description}</Banner.Description>
</Banner.Content>
</Banner.Base>
))}
</div>Container
container="card", the default, has a 12px radius. section has square corners, for a full-width message at the top of a page. Both fill their parent.
<Banner.Base
variant="information"
container="section"
showCloseButton={false}
>
<Banner.Icon
icon={<IconInterfaceEssentialInfoInformationCircle aria-hidden />}
/>
<Banner.Content>
<Banner.Title>You are viewing last year's class</Banner.Title>
<Banner.Description>
Changes here do not affect this term.
</Banner.Description>
</Banner.Content>
</Banner.Base>Action
Banner.Action holds your controls beside the text. It wraps below the text when less than 240px remains. It does not restyle its children, so pass a Secondary XSmall Button in the status colour.
<Banner.Base variant="error" showCloseButton={false}>
<Banner.Icon
icon={<IconInterfaceEssentialDeleteDisabled aria-hidden />}
/>
<Banner.Content>
<Banner.Title>Sync failed</Banner.Title>
<Banner.Description>
Attendance from 9 May did not upload.
</Banner.Description>
</Banner.Content>
<Banner.Action>
<Button type="button" color="destructive" variant="secondary" size="xs">
Retry
</Button>
</Banner.Action>
</Banner.Base>Close
The close button shows by default. A click calls onClose with no arguments. The Banner keeps no visibility state, so the app unmounts it. showCloseButton={false} removes the button.
closeButtonLabel renames it, "Close alert" by default. customCloseIcon replaces the X glyph and closeButtonClassName merges last into the button.
<div className="flex w-full flex-col items-center gap-spacing-12">
{visible ? (
<Banner.Base variant="success" onClose={() => setVisible(false)}>
<Banner.Icon icon={<IconInterfaceEssentialDoneCheck aria-hidden />} />
<Banner.Content>
<Banner.Title>Grades submitted</Banner.Title>
<Banner.Description>
Parents can see them from tomorrow.
</Banner.Description>
</Banner.Content>
</Banner.Base>
) : (
<Button variant="secondary-accent" onClick={() => setVisible(true)}>
Show the banner again
</Button>
)}
</div>Disabled and loading
disabled greys the title and the description and disables the close and expand buttons. Controls in Banner.Action keep their own disabled state, so disable them yourself.
loading replaces every child and both buttons with an aria-hidden skeleton and sets aria-busy.
<div className="flex w-full flex-col gap-spacing-12">
<Banner.Base variant="information" disabled>
<Banner.Icon
icon={<IconInterfaceEssentialInfoInformationCircle aria-hidden />}
/>
<Banner.Content>
<Banner.Title>Enrolment is closed</Banner.Title>
<Banner.Description>It opens again on 1 June.</Banner.Description>
</Banner.Content>
</Banner.Base>
<Banner.Base variant="information" loading>
<Banner.Icon
icon={<IconInterfaceEssentialInfoInformationCircle aria-hidden />}
/>
<Banner.Content>
<Banner.Title>Enrolment is closed</Banner.Title>
<Banner.Description>It opens again on 1 June.</Banner.Description>
</Banner.Content>
</Banner.Base>
</div>Details panel
A Banner.Panel adds an expand button to the header and opens below it at the full width. The panel is unstyled, so the app sets its padding, fill and border.
expandButtonLabel names the expand button, "Details" by default. expanded, defaultExpanded and onExpandedChange control it. Use one Panel per Banner.
<Banner.Base
variant="information"
showCloseButton={false}
expandButtonLabel="Release notes"
>
<Banner.Icon
icon={<IconInterfaceEssentialInfoInformationCircle aria-hidden />}
/>
<Banner.Content>
<Banner.Title>Update available</Banner.Title>
<Banner.Description>Reload when you are ready.</Banner.Description>
</Banner.Content>
<Banner.Panel
className={`flex flex-col gap-spacing-8 border-t-(length:--border-border-1) border-color-border bg-color-background-surface px-spacing-16 py-spacing-12 ${typography('body-regular')} text-color-text-primary`}
>
Lesson plans now sort by unit. Attendance exports include late arrivals.
</Banner.Panel>
</Banner.Base>Accessibility
errorandwarningdefault torole="alert". The other variants userole="status". Aroleprop overrides the default.- The close and expand buttons are
type="button", so a Banner inside a form never submits it. - The expand button carries
aria-expanded. - Give a custom icon accessible text when it adds information. Otherwise mark it
aria-hidden. Banner.IconandBanner.Panelmust be direct children ofBanner.Baseor sit inside fragments.ContentandActioncan be wrapped in your own components.- A
rendertarget must keep the root role and the disclosure attributes.
API
Banner.Base
Prop
Type
Banner.Icon
Prop
Type
Banner.Content, Banner.Title, Banner.Description, Banner.Action
Prop
Type
Banner.Panel
Prop
Type