brut-ui
uidialogsource: ok · hit
Dialog
A strongly elevated modal that preserves Base UI focus and keyboard behavior.
npx shadcn add "https://www.brut-ui.site/r/dialog.json"free
Where it goes
kanban
Interaction
modal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightui-primitives@base-ui/react
iconslucide-react
Registry dependencies
utils
Files3 · source cached 10/8/2026, 12:52:53 PM
registry/brutalist/ui/dialog.tsx
"use client"
import * as React from "react"
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/brutalist/ui/button"
import { XIcon } from "lucide-react"
import "../styles/brutalist.css"
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: DialogPrimitive.Backdrop.Props) {
return (
<DialogPrimitive.Backdrop
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 isolate z-50 bg-foreground/35 duration-100 supports-backdrop-filter:backdrop-blur-[2px] data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: DialogPrimitive.Popup.Props & {
showCloseButton?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
"brut-dialog fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 overflow-hidden bg-popover p-5 text-sm text-popover-foreground duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
render={
<Button
variant="ghost"
className="absolute top-2 right-2"
size="icon-sm"
/>
}
>
<XIcon
/>
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Popup>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"-mx-5 -mb-5 flex flex-col-reverse gap-3 border-t-[var(--brut-border)] border-border bg-muted p-5 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close render={<Button variant="outline" />}>
Close
</DialogPrimitive.Close>
)}
</div>
)
}
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn(
"text-xl leading-none font-black tracking-tight",
className
)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: DialogPrimitive.Description.Props) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className
)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}
registry/brutalist/ui/button.tsx
import { Button as ButtonPrimitive } from "@base-ui/react/button"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import "../styles/brutalist.css"
const buttonVariants = cva(
"group/button brut-focus inline-flex shrink-0 items-center justify-center bg-clip-padding text-sm whitespace-nowrap outline-none select-none disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
default: "brut-control bg-primary text-primary-foreground hover:bg-primary/90",
outline:
"brut-control bg-background text-foreground hover:bg-muted aria-expanded:bg-muted",
secondary:
"brut-control bg-secondary text-secondary-foreground hover:bg-secondary/85 aria-expanded:bg-secondary",
ghost:
"brut-quiet hover:text-foreground aria-expanded:bg-muted",
destructive:
"brut-control bg-destructive text-white hover:bg-destructive/90",
link: "brut-quiet text-foreground underline decoration-[3px] underline-offset-4 hover:bg-transparent",
},
size: {
default:
"h-9 gap-2 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-11 gap-2 px-5 text-base has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4",
icon: "size-9",
"icon-xs":
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm":
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
...props
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
return (
<ButtonPrimitive
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
registry/brutalist/styles/brutalist.css
:root,
[data-brut-palette="paper"] {
--background: oklch(0.985 0.004 95);
--foreground: oklch(0.15 0.01 65);
--card: oklch(1 0 0);
--card-foreground: var(--foreground);
--popover: oklch(1 0 0);
--popover-foreground: var(--foreground);
--primary: oklch(0.82 0.18 88);
--primary-foreground: oklch(0.13 0.01 70);
--secondary: oklch(0.88 0.105 210);
--secondary-foreground: oklch(0.13 0.01 70);
--muted: oklch(0.94 0.018 90);
--muted-foreground: oklch(0.38 0.02 65);
--accent: oklch(0.72 0.2 20);
--accent-foreground: oklch(0.99 0 0);
--destructive: oklch(0.61 0.24 28);
--border: oklch(0.15 0.01 65);
--input: oklch(0.15 0.01 65);
--ring: oklch(0.56 0.23 264);
--brut-shadow-color: oklch(0.15 0.01 65);
}
:root {
--chart-1: var(--primary);
--chart-2: var(--secondary);
--chart-3: var(--accent);
--chart-4: var(--destructive);
--chart-5: var(--muted-foreground);
--sidebar: var(--card);
--sidebar-foreground: var(--card-foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--accent);
--sidebar-accent-foreground: var(--accent-foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
[data-brut-palette="signal"] {
--background: oklch(0.91 0.16 96);
--foreground: oklch(0.13 0.01 70);
--card: oklch(0.985 0.025 96);
--card-foreground: var(--foreground);
--popover: oklch(0.985 0.025 96);
--popover-foreground: var(--foreground);
--primary: oklch(0.15 0.01 70);
--primary-foreground: oklch(0.93 0.17 96);
--secondary: oklch(0.73 0.2 28);
--secondary-foreground: oklch(0.13 0.01 70);
--muted: oklch(0.86 0.11 96);
--muted-foreground: oklch(0.31 0.03 70);
--accent: oklch(0.75 0.13 222);
--accent-foreground: oklch(0.13 0.01 70);
--destructive: oklch(0.55 0.24 28);
--border: oklch(0.13 0.01 70);
--input: oklch(0.13 0.01 70);
--ring: oklch(0.55 0.24 28);
--brut-shadow-color: oklch(0.13 0.01 70);
}
[data-brut-palette="sky"] {
--background: oklch(0.92 0.055 235);
--foreground: oklch(0.16 0.04 255);
--card: oklch(0.98 0.018 230);
--card-foreground: var(--foreground);
--popover: oklch(0.985 0.018 230);
--popover-foreground: var(--foreground);
--primary: oklch(0.52 0.2 255);
--primary-foreground: oklch(0.99 0 0);
--secondary: oklch(0.88 0.16 92);
--secondary-foreground: oklch(0.16 0.04 255);
--muted: oklch(0.86 0.045 235);
--muted-foreground: oklch(0.36 0.055 255);
--accent: oklch(0.71 0.18 28);
--accent-foreground: oklch(0.16 0.04 255);
--destructive: oklch(0.56 0.24 27);
--border: oklch(0.16 0.04 255);
--input: oklch(0.16 0.04 255);
--ring: oklch(0.67 0.2 28);
--brut-shadow-color: oklch(0.16 0.04 255);
}
:root,
[data-brut-strength="soft"] {
--brut-border: 1px;
--brut-shadow-x: 2px;
--brut-shadow-y: 2px;
--brut-panel-shadow-x: 4px;
--brut-panel-shadow-y: 4px;
--brut-slider-track: 2px;
--brut-hover: -1px;
--brut-press: 1px;
--brut-weight: 600;
}
[data-brut-strength="solid"] {
--brut-border: 2px;
--brut-shadow-x: 4px;
--brut-shadow-y: 4px;
--brut-panel-shadow-x: 7px;
--brut-panel-shadow-y: 7px;
--brut-slider-track: 4px;
--brut-hover: -1px;
--brut-press: 2px;
--brut-weight: 700;
}
[data-brut-strength="loud"] {
--brut-border: 3px;
--brut-shadow-x: 6px;
--brut-shadow-y: 6px;
--brut-panel-shadow-x: 9px;
--brut-panel-shadow-y: 9px;
--brut-slider-track: 6px;
--brut-hover: -2px;
--brut-press: 3px;
--brut-weight: 800;
}
:root,
[data-brut-radius="cut"] {
--radius: 0.375rem;
}
[data-brut-radius="sharp"] {
--radius: 0rem;
}
[data-brut-radius="chunky"] {
--radius: 0.875rem;
}
@layer base {
* {
border-color: var(--border);
outline-color: color-mix(in oklab, var(--ring) 60%, transparent);
}
html {
scroll-behavior: smooth;
}
body {
background-color: var(--background);
color: var(--foreground);
}
::selection {
background: var(--primary);
color: var(--primary-foreground);
}
}
@layer components {
.brut-panel {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: var(--brut-panel-shadow-x) var(--brut-panel-shadow-y) 0 var(--brut-shadow-color);
}
.brut-surface {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: var(--brut-shadow-x) var(--brut-shadow-y) 0 var(--brut-shadow-color);
}
.brut-control {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: var(--brut-shadow-x) var(--brut-shadow-y) 0 var(--brut-shadow-color);
font-weight: var(--brut-weight);
transition: transform 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
}
.brut-control:hover:not(:disabled, [data-disabled]) {
transform: translate(var(--brut-hover), var(--brut-hover));
box-shadow: calc(var(--brut-shadow-x) - var(--brut-hover)) calc(var(--brut-shadow-y) - var(--brut-hover)) 0 var(--brut-shadow-color);
}
.brut-control:active:not(:disabled, [data-disabled]),
.brut-control[data-pressed] {
transform: translate(var(--brut-press), var(--brut-press));
box-shadow: 0 0 0 var(--brut-shadow-color);
}
.brut-quiet {
border: var(--brut-border) solid transparent;
border-radius: var(--radius);
box-shadow: none;
font-weight: var(--brut-weight);
}
.brut-quiet:hover {
border-color: var(--border);
background: var(--muted);
}
.brut-focus:focus-visible {
outline: 3px solid var(--ring);
outline-offset: 3px;
}
.brut-input {
border: var(--brut-border) solid var(--input);
border-radius: var(--radius);
background: var(--card);
box-shadow: var(--brut-shadow-x) var(--brut-shadow-y) 0 var(--brut-shadow-color);
font-weight: 600;
transition: box-shadow 120ms ease, transform 120ms ease;
}
.brut-input:focus-visible {
outline: 3px solid var(--ring);
outline-offset: 2px;
transform: translate(-1px, -1px);
box-shadow: calc(var(--brut-shadow-x) + 2px) calc(var(--brut-shadow-y) + 2px) 0 var(--brut-shadow-color);
}
.brut-input-group {
border: var(--brut-border) solid var(--input);
border-radius: var(--radius);
background: var(--card);
box-shadow: var(--brut-shadow-x) var(--brut-shadow-y) 0 var(--brut-shadow-color);
font-weight: 600;
transition: box-shadow 120ms ease, transform 120ms ease;
}
.brut-input-group:focus-within {
outline: 3px solid var(--ring);
outline-offset: 2px;
transform: translate(-1px, -1px);
box-shadow: calc(var(--brut-shadow-x) + 2px) calc(var(--brut-shadow-y) + 2px) 0 var(--brut-shadow-color);
}
.brut-input-group:has([aria-invalid="true"]) {
border-color: var(--destructive);
}
.brut-input-group:has([aria-invalid="true"]):focus-within {
outline-color: var(--destructive);
}
.brut-otp-group:has([data-active="true"]) {
outline: 3px solid var(--ring);
outline-offset: 2px;
transform: translate(-1px, -1px);
box-shadow: calc(var(--brut-shadow-x) + 2px) calc(var(--brut-shadow-y) + 2px) 0 var(--brut-shadow-color);
}
.brut-otp-group:has([aria-invalid="true"]) {
border-color: var(--destructive);
}
.brut-otp-group:has([aria-invalid="true"]):has([data-active="true"]) {
outline-color: var(--destructive);
}
.brut-chip {
border: var(--brut-border) solid var(--border);
border-radius: calc(var(--radius) * 0.75);
box-shadow: none;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.brut-toggle {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: none;
font-weight: var(--brut-weight);
}
.brut-kbd {
border: var(--brut-border) solid var(--border);
border-radius: calc(var(--radius) * 0.6);
box-shadow: none;
font-weight: var(--brut-weight);
}
.brut-item {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: none;
}
.brut-accordion-item:not(:last-child) {
border-bottom: var(--brut-border) solid var(--border);
box-shadow: none;
}
.brut-check {
border: var(--brut-border) solid var(--border);
border-radius: calc(var(--radius) * 0.5);
background: var(--card);
box-shadow: 2px 2px 0 var(--brut-shadow-color);
}
.brut-radio {
border: var(--brut-border) solid var(--input);
}
.brut-slider-thumb {
border: var(--brut-border) solid var(--ring);
}
.brut-menubar {
border: var(--brut-border) solid var(--border);
}
.brut-avatar {
border: var(--brut-border) solid var(--border);
background: var(--card);
box-shadow: calc(var(--brut-border) + 1px) calc(var(--brut-border) + 1px) 0 var(--brut-shadow-color);
}
.brut-separator {
background: var(--border);
}
.brut-dialog {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: var(--brut-panel-shadow-x) var(--brut-panel-shadow-y) 0 var(--brut-shadow-color);
}
.brut-questionnaire-choice {
border: var(--brut-border) solid var(--input);
border-radius: var(--radius);
}
.brut-sheet[data-side="right"] {
border-left: var(--brut-border) solid var(--border);
border-radius: var(--radius) 0 0 var(--radius);
box-shadow: calc(0px - var(--brut-panel-shadow-x)) 0 0 var(--brut-shadow-color);
}
.brut-sheet[data-side="left"] {
border-right: var(--brut-border) solid var(--border);
border-radius: 0 var(--radius) var(--radius) 0;
box-shadow: var(--brut-panel-shadow-x) 0 0 var(--brut-shadow-color);
}
.brut-sheet[data-side="bottom"] {
border-top: var(--brut-border) solid var(--border);
border-radius: var(--radius) var(--radius) 0 0;
box-shadow: 0 calc(0px - var(--brut-panel-shadow-y)) 0 var(--brut-shadow-color);
}
.brut-sheet[data-side="top"] {
border-bottom: var(--brut-border) solid var(--border);
border-radius: 0 0 var(--radius) var(--radius);
box-shadow: 0 var(--brut-panel-shadow-y) 0 var(--brut-shadow-color);
}
.brut-drawer[data-swipe-direction="down"] {
border-top: var(--brut-border) solid var(--border);
border-radius: var(--radius) var(--radius) 0 0;
box-shadow: 0 calc(0px - var(--brut-panel-shadow-y)) 0 var(--brut-shadow-color);
}
.brut-drawer[data-swipe-direction="up"] {
border-bottom: var(--brut-border) solid var(--border);
border-radius: 0 0 var(--radius) var(--radius);
box-shadow: 0 var(--brut-panel-shadow-y) 0 var(--brut-shadow-color);
}
.brut-drawer[data-swipe-direction="left"] {
border-right: var(--brut-border) solid var(--border);
border-radius: 0 var(--radius) var(--radius) 0;
box-shadow: var(--brut-panel-shadow-x) 0 0 var(--brut-shadow-color);
}
.brut-drawer[data-swipe-direction="right"] {
border-left: var(--brut-border) solid var(--border);
border-radius: var(--radius) 0 0 var(--radius);
box-shadow: calc(0px - var(--brut-panel-shadow-x)) 0 0 var(--brut-shadow-color);
}
.brut-popover {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: var(--brut-shadow-x) var(--brut-shadow-y) 0 var(--brut-shadow-color);
}
.brut-switch {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
box-shadow: 3px 3px 0 var(--brut-shadow-color);
}
.brut-switch-thumb {
border-radius: calc(var(--radius) * 0.75);
}
.brut-progress {
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
background: var(--card);
}
.brut-resizable-group {
overflow: hidden;
border: var(--brut-border) solid var(--border);
border-radius: var(--radius);
background: var(--card);
}
.brut-resizable-handle {
width: var(--brut-border);
background: var(--border);
}
.brut-resizable-handle[aria-orientation="horizontal"] {
width: 100%;
height: var(--brut-border);
}
.brut-resizable-grip {
border: var(--brut-border) solid var(--border);
border-radius: min(var(--radius), 0.375rem);
background: var(--card);
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}