Skip to content
Components

Surface

Groups related content on an opaque paper or translucent glass layer.

Storage

Workspace usage this month

18.4 GB

of 50 GB used

Playground

material

Sets an opaque paper or translucent glass surface.

interactive

Makes this example render the surface as a focusable button.

Usage

tsx
import { Surface } from "earthling-ui/surface";

export function Example() {
  return (
    <>
      <Surface material="paper">
        <h3>Storage</h3>
        <p>18.4 GB of 50 GB used.</p>
      </Surface>
    </>
  );
}
  • Use paper for dependable contrast and glass only where the background supports translucency.
  • When interactive is true, render Surface as a semantic button or link with asChild.
  • Avoid nesting equal corner radii; reduce the inner radius by the surrounding inset.

Installation

Start with the project setup for React and Tailwind CSS 4. Both paths use the same component and theme.

bash
npm install earthling-ui

Use the component import shown above. Package updates keep the implementation current.

API reference

Declarations from the current library build, including inherited primitive props. Playground controls above show selected options; they are not the complete API.

tsx
// Generated by dts-bundle-generator v9.5.1

import { VariantProps } from 'class-variance-authority';
import { ComponentProps } from 'react';

declare const surfaceVariants: (props?: ({
	material?: "paper" | "glass" | null | undefined;
	interactive?: boolean | null | undefined;
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
export interface SurfaceProps extends ComponentProps<"div">, VariantProps<typeof surfaceVariants> {
	asChild?: boolean;
}
export declare const Surface: import("react").ForwardRefExoticComponent<Omit<SurfaceProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;

export {};

Runtime dependencies: @radix-ui/react-slot, class-variance-authority, cnfast.

Source

Use the eject command to copy this implementation with its required helpers and project-specific imports.

View Surface implementation
tsx
"use client";

import { cn } from "@/utils/cn";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { type ComponentProps, forwardRef } from "react";

const surfaceVariants = cva(
  "relative flow-root rounded-lg border p-4 text-foreground",
  {
    variants: {
      material: {
        paper: "border-current/10 bg-surface shadow-xs",
        glass:
          "border-current/10 bg-surface/75 shadow-xs backdrop-blur-sm before:pointer-events-none before:absolute before:inset-[-1px] before:rounded-[inherit] before:bg-[linear-gradient(var(--color-light),transparent_45%)] before:p-px before:[mask-clip:content-box,_border-box] before:[mask-composite:exclude] before:[mask-image:linear-gradient(#000,#000),_linear-gradient(#000,#000)] before:[mask-origin:content-box,_border-box] before:select-none",
      },
      interactive: {
        true: "cursor-pointer hover:border-current/20 hover:bg-current/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-outline",
        false: "",
      },
    },
    compoundVariants: [
      {
        material: "glass",
        interactive: true,
        className: "hover:border-current/20 hover:bg-surface/90",
      },
    ],
    defaultVariants: {
      interactive: false,
      material: "glass",
    },
  },
);

export interface SurfaceProps
  extends ComponentProps<"div">, VariantProps<typeof surfaceVariants> {
  asChild?: boolean;
}

const Surface = forwardRef<HTMLDivElement, SurfaceProps>(
  ({ className, asChild, interactive, material, ...props }, ref) => {
    const Comp = asChild ? Slot : "div";
    return (
      <Comp
        className={cn(surfaceVariants({ interactive, material }), className)}
        ref={ref}
        {...props}
      />
    );
  },
);
Surface.displayName = "Surface";

export { Surface };