V1.0

Base Components

Modal

Modal is a floating surface used to display transient content such as confirmation actions, selection options, and more.

Installation

Install the following dependencies:

terminal
npm install @radix-ui/react-dialog

Create a modal.tsx file and paste the following code into it.

/components/ui/modal.tsx
// AlignUI Modal v0.0.0
 
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { RiCloseLine, type RemixiconComponentType } from '@remixicon/react';
 
import * as CompactButton from '@/components/ui/compact-button';
import { cnExt } from '@/utils/cn';
 
const ModalRoot = DialogPrimitive.Root;
const ModalTrigger = DialogPrimitive.Trigger;
const ModalClose = DialogPrimitive.Close;
const ModalPortal = DialogPrimitive.Portal;
 
const ModalOverlay = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...rest }, forwardedRef) => {
  return (
    <DialogPrimitive.Overlay
      ref={forwardedRef}
      className={cnExt(
        // base
        'fixed inset-0 z-50 flex flex-col items-center justify-center overflow-y-auto bg-overlay p-4 backdrop-blur-[10px]',
        // animation
        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
        className,
      )}
      {...rest}
    />
  );
});
ModalOverlay.displayName = 'ModalOverlay';
 
const ModalContent = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
    overlayClassName?: string;
    showClose?: boolean;
  }
>(
  (
    { className, overlayClassName, children, showClose = true, ...rest },
    forwardedRef,
  ) => {
    return (
      <ModalPortal>
        <ModalOverlay className={overlayClassName}>
          <DialogPrimitive.Content
            ref={forwardedRef}
            className={cnExt(
              // base
              'relative w-full max-w-[400px]',
              'rounded-20 bg-bg-white-0 shadow-regular-md',
              // focus
              'focus:outline-none',
              // animation
              'data-[state=open]:animate-in data-[state=closed]:animate-out',
              'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
              'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
              className,
            )}
            {...rest}
          >
            {children}
            {showClose && (
              <ModalClose asChild>
                <CompactButton.Root
                  variant='ghost'
                  size='large'
                  className='absolute right-4 top-4'
                >
                  <CompactButton.Icon as={RiCloseLine} />
                </CompactButton.Root>
              </ModalClose>
            )}
          </DialogPrimitive.Content>
        </ModalOverlay>
      </ModalPortal>
    );
  },
);
ModalContent.displayName = 'ModalContent';
 
function ModalHeader({
  className,
  children,
  icon: Icon,
  title,
  description,
  ...rest
}: React.HTMLAttributes<HTMLDivElement> & {
  icon?: RemixiconComponentType;
  title?: string;
  description?: string;
}) {
  return (
    <div
      className={cnExt(
        'relative flex items-start gap-3.5 py-4 pl-5 pr-14 before:absolute before:inset-x-0 before:bottom-0 before:border-b before:border-stroke-soft-200',
        className,
      )}
      {...rest}
    >
      {children || (
        <>
          {Icon && (
            <div className='flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-white-0 ring-1 ring-inset ring-stroke-soft-200'>
              <Icon className='size-5 text-text-sub-600' />
            </div>
          )}
          {(title || description) && (
            <div className='flex-1 space-y-1'>
              {title && <ModalTitle>{title}</ModalTitle>}
              {description && (
                <ModalDescription>{description}</ModalDescription>
              )}
            </div>
          )}
        </>
      )}
    </div>
  );
}
ModalHeader.displayName = 'ModalHeader';
 
const ModalTitle = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...rest }, forwardedRef) => {
  return (
    <DialogPrimitive.Title
      ref={forwardedRef}
      className={cnExt('text-label-sm text-text-strong-950', className)}
      {...rest}
    />
  );
});
ModalTitle.displayName = 'ModalTitle';
 
const ModalDescription = React.forwardRef<
  React.ComponentRef<typeof DialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...rest }, forwardedRef) => {
  return (
    <DialogPrimitive.Description
      ref={forwardedRef}
      className={cnExt('text-paragraph-xs text-text-sub-600', className)}
      {...rest}
    />
  );
});
ModalDescription.displayName = 'ModalDescription';
 
function ModalBody({
  className,
  ...rest
}: React.HTMLAttributes<HTMLDivElement>) {
  return <div className={cnExt('p-5', className)} {...rest} />;
}
ModalBody.displayName = 'ModalBody';
 
function ModalFooter({
  className,
  ...rest
}: React.HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      className={cnExt(
        'flex items-center justify-between gap-3 border-t border-stroke-soft-200 px-5 py-4',
        className,
      )}
      {...rest}
    />
  );
}
 
ModalFooter.displayName = 'ModalFooter';
 
export {
  ModalRoot as Root,
  ModalTrigger as Trigger,
  ModalClose as Close,
  ModalPortal as Portal,
  ModalOverlay as Overlay,
  ModalContent as Content,
  ModalHeader as Header,
  ModalTitle as Title,
  ModalDescription as Description,
  ModalBody as Body,
  ModalFooter as Footer,
};

Update the import paths to match your project setup.

Examples

With Header

API Reference

This component is based on the Radix UI Dialog primitives. Refer to their documentation for the API reference.

Modal.Root

This component is based on the Dialog.Root primitive.

Modal.Trigger

This component is based on the Dialog.Trigger primitive.

Modal.Close

This component is based on the Dialog.Close primitive.

Modal.Portal

This component is based on the Dialog.Portal primitive.

Modal.Overlay

This component is based on the Dialog.Overlay primitive.

Modal.Content

This component is based on the Dialog.Content primitive. Wrapped by Portal and Overlay.

Modal.Header

Used to display the header section of a modal dialog. It accepts icon, title, description if children is not provided.

This component is based on the <div> element and supports all of its props. And adds:

PropTypeDefault
icon
RemixiconComponentType
title
string
description
string

Modal.Title

This component is based on the Dialog.Title primitive.

Modal.Description

This component is based on the Dialog.Description primitive.

Modal.Body

Used for the main content area of a modal dialog.

This component is based on the <div> element and supports all of its props.

Modal.Footer

Used for the footer section of a modal dialog. It typically contains action buttons like "Save" or "Cancel."

This component is based on the <div> element and supports all of its props.

© 2024 AlignUI Design System. All rights reserved.