+
+
+ Basic usage:
+
+
+
+ {apiKey}
+
+
-
-
{apiKey}
-
+
+
+
+ Different variants:
+
+
+
+
+
+
+ With custom styling:
+
+
diff --git a/apps/engineering/content/design/components/buttons/copy-button.mdx b/apps/engineering/content/design/components/buttons/copy-button.mdx
index e710e1e13e..9ce262c55d 100644
--- a/apps/engineering/content/design/components/buttons/copy-button.mdx
+++ b/apps/engineering/content/design/components/buttons/copy-button.mdx
@@ -1,38 +1,67 @@
---
title: CopyButton
-summary: A button component that copies text to clipboard with visual feedback
+summary: A button component that copies text to clipboard with visual feedback and accessibility features
---
import { Default } from "./copy-button.examples"
-The `CopyButton` component provides a simple way to copy text to the clipboard with visual feedback. When clicked, it copies the provided text and shows a checkmark icon to indicate success.
+The `CopyButton` component provides a simple way to copy text to the clipboard with visual feedback. It extends the base Button component and includes built-in copy functionality with proper accessibility support.
## Features
-- Visual feedback with icon change on copy
-- Accessible with screen reader support
+- Visual feedback with icon change on copy (TaskUnchecked → TaskChecked)
+- Accessible with screen reader support and ARIA labels
- Automatic reset after 2 seconds
-- Customizable through className prop
-- TypeScript support
+- Extends ButtonProps for full button customization
+- Analytics support with optional src prop
+- Prevents event propagation to parent elements
+- TypeScript support with proper typing
## Props
-| Prop | Type | Description |
-|------|------|-------------|
-| `value` | `string` | The text content to be copied to clipboard |
-| `src` | `string?` | Optional source identifier for analytics |
-| `className` | `string?` | Additional CSS classes to apply to the button |
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `value` | `string` | **Required** | The text content to be copied to clipboard |
+| `src` | `string?` | `undefined` | Optional source identifier for analytics tracking |
+| `variant` | `ButtonVariant` | `"outline"` | Button variant (outline, ghost, primary, etc.) |
+| `className` | `string?` | `undefined` | Additional CSS classes to apply to the button |
+
+The component also accepts all standard Button props.
## Usage
+## Behavior
+
+1. **Click Handling**: On click, the button copies the provided text to the clipboard
+2. **Visual Feedback**: The icon changes from TaskUnchecked to TaskChecked state
+3. **Auto Reset**: After 2 seconds, the icon reverts to its original state
+4. **Event Prevention**: Parent click events are prevented from triggering (`e.stopPropagation`)
+5. **Analytics**: If provided, the `src` prop is passed to analytics for tracking
+
## Accessibility
-The component includes screen reader support with an appropriate "Copy" label. The visual state change (unchecked to checked icon) provides clear feedback for all users.
+The component includes comprehensive accessibility features:
-## Behavior
+- **ARIA Label**: `aria-label="Copy to clipboard"` for screen readers
+- **Screen Reader Text**: Hidden "Copy" text for additional context
+- **Title Attribute**: `title="Copy to clipboard"` for tooltip on hover
+- **Focus Management**: Proper focus states with `focus:ring-0 focus:border-grayA-6`
+- **Keyboard Support**: Full keyboard navigation support inherited from Button component
+
+## Styling
+
+The component includes default styling:
+
+- Fixed dimensions: `w-6 h-6` (24x24px)
+- Focus states: `focus:ring-0 focus:border-grayA-6`
+- Icons: Full-size icons with `w-full h-full`
+- Default variant: `outline` for subtle appearance
+
+## Best Practices
-1. On click, the button copies the provided text to the clipboard
-2. The icon changes from an unchecked to checked state
-3. After 2 seconds, the icon reverts to its original state
-4. Parent click events are prevented from triggering (e.stopPropagation)
+- Use the `src` prop for analytics tracking when copying sensitive data
+- Provide meaningful `value` content for better user experience
+- Consider the button's context when choosing variants
+- Ensure sufficient contrast for the button in your design
+- Test with screen readers to verify accessibility
diff --git a/apps/engineering/content/design/components/code.example.tsx b/apps/engineering/content/design/components/code.example.tsx
new file mode 100644
index 0000000000..848d40d98f
--- /dev/null
+++ b/apps/engineering/content/design/components/code.example.tsx
@@ -0,0 +1,62 @@
+"use client";
+import { RenderComponentWithSnippet } from "@/app/components/render";
+import { Code, CopyButton, VisibleButton } from "@unkey/ui";
+import { useState } from "react";
+
+const EXAMPLE_SNIPPET = `curl -XPOST 'https://api.unkey.dev/v1/ratelimits.limit' \\
+ -H 'Content-Type: application/json' \\
+ -H 'Authorization: Bearer
' \\
+ -d '{
+ "namespace": "demo_namespace",
+ "identifier": "user_123",
+ "limit": 10,
+ "duration": 10000
+ }'`;
+
+export function CodeExample() {
+ const [showKeyInSnippet, setShowKeyInSnippet] = useState(false);
+
+ return (
+
+
+
+
+ }
+ visibleButton={
+ setShowKeyInSnippet(visible)}
+ title="Key Snippet"
+ />
+ }
+ >
+ {showKeyInSnippet
+ ? EXAMPLE_SNIPPET
+ : EXAMPLE_SNIPPET.replace("", "********")}
+
+
+
+
+ );
+}
+
+export function CodeVariants() {
+ return (
+
+
+
+
+ Default Variant
+ Ghost Variant
+ Legacy Variant
+
+
+
+
+ );
+}
diff --git a/apps/engineering/content/design/components/code.mdx b/apps/engineering/content/design/components/code.mdx
new file mode 100644
index 0000000000..82eeb26688
--- /dev/null
+++ b/apps/engineering/content/design/components/code.mdx
@@ -0,0 +1,116 @@
+---
+title: Code
+description: A versatile code component for displaying inline and block code snippets with customizable styling and integrated buttons.
+---
+
+import { CodeExample, CodeVariants } from "./code.example";
+
+# Code
+
+The Code component provides a consistent way to display code snippets within your application. It supports both inline and block code display with customizable styling options and integrated button functionality.
+
+## Features
+
+- Multiple variants (default, ghost, legacy)
+- Integrated copy and visibility buttons
+- Consistent monospace font
+- Customizable styling
+- Accessible by default
+- Responsive design
+- Focus states for better interaction
+
+## Usage
+
+```tsx
+import { Code, CopyButton, VisibleButton } from "@unkey/ui";
+
+function MyComponent() {
+ return (
+ }
+ visibleButton={ {}} />}
+ >
+ const hello = "world";
+
+ );
+}
+```
+
+## Examples
+
+### Default Variant with Buttons
+
+The default variant provides a subtle background with a border and integrated buttons.
+
+
+
+### Variant Styles
+
+The Code component supports different visual styles.
+
+
+
+## Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `variant` | `"default" \| "ghost" \| "legacy"` | `"default"` | The visual style of the code component |
+| `className` | `string` | `undefined` | Additional CSS classes for the wrapper div |
+| `buttonsClassName` | `string` | `undefined` | Additional CSS classes for the buttons container |
+| `preClassName` | `string` | `undefined` | Additional CSS classes for the pre element |
+| `copyButton` | `React.ReactNode` | `undefined` | Copy button component to display |
+| `visibleButton` | `React.ReactNode` | `undefined` | Visibility toggle button component |
+
+The component also accepts all standard HTML pre element props.
+
+## Variants
+
+### Default
+- Subtle background with border
+- White background in light mode, black in dark mode
+- Gray border for visual separation
+
+### Ghost
+- Transparent background
+- No border
+- Minimal visual impact
+
+### Legacy
+- Primary text color
+- Subtle background
+- Hover effects with primary border
+- Rounded corners
+
+## Styling
+
+The Code component includes:
+
+- Monospace font for code readability
+- Consistent padding and border radius
+- Focus states for keyboard navigation
+- Hover effects for better interaction
+- Dark mode support
+- Responsive design
+- Flexible button positioning
+
+## Best Practices
+
+- Use the default variant for code snippets with buttons
+- Use the ghost variant when you need minimal visual impact
+- Use the legacy variant for backward compatibility
+- Add appropriate padding around the code component
+- Consider using syntax highlighting for complex code blocks
+- Ensure sufficient contrast between code and background
+- Use semantic HTML structure for better accessibility
+- Position buttons appropriately using the buttonsClassName prop
+
+## Accessibility
+
+The Code component is built with accessibility in mind:
+
+- Proper ARIA attributes
+- Keyboard focus management
+- High contrast text
+- Semantic HTML structure
+- Screen reader support
+- Focus states for interactive elements
\ No newline at end of file
diff --git a/internal/ui/src/components/code.tsx b/internal/ui/src/components/code.tsx
new file mode 100644
index 0000000000..332eec6e63
--- /dev/null
+++ b/internal/ui/src/components/code.tsx
@@ -0,0 +1,68 @@
+"use client";
+import { type VariantProps, cva } from "class-variance-authority";
+import type * as React from "react";
+import { cn } from "../lib/utils";
+
+const codeVariants = cva(
+ "flex w-full px-4 border rounded-xl ring-0 flex items-start justify-between ph-no-capture whitespace-pre-wrap break-all ",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-grayA-5 focus:outline-none focus:ring-0 bg-white dark:bg-black text-[11px] py-2",
+ ghost: "border-none bg-transparent text-[11px] py-2",
+ legacy:
+ "text-primary bg-background-subtle rounded-md hover:border-primary focus:outline-none focus:ring-0 border-grayA-4",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+export interface CodeProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ className?: string;
+ buttonsClassName?: string;
+ preClassName?: string;
+ variant?: "default" | "ghost" | "legacy";
+ copyButton?: React.ReactNode;
+ visibleButton?: React.ReactNode;
+}
+
+function Code({
+ className,
+ variant,
+ copyButton,
+ visibleButton,
+ buttonsClassName,
+ preClassName,
+ ...props
+}: CodeProps) {
+ return (
+
+
+
+ {visibleButton}
+ {copyButton}
+
+
+ );
+}
+
+Code.displayName = "Code";
+
+export { Code, codeVariants };
diff --git a/internal/ui/src/components/copy-button.tsx b/internal/ui/src/components/copy-button.tsx
index 4f0a456a72..381ad7487a 100644
--- a/internal/ui/src/components/copy-button.tsx
+++ b/internal/ui/src/components/copy-button.tsx
@@ -2,18 +2,25 @@
import { TaskChecked, TaskUnchecked } from "@unkey/icons";
import * as React from "react";
import { cn } from "../lib/utils";
+import { Button, type ButtonProps } from "./button";
-interface CopyButtonProps extends React.HTMLAttributes {
+type CopyButtonProps = ButtonProps & {
+ /**
+ * The value to copy to clipboard
+ */
value: string;
+ /**
+ * Source component for analytics
+ */
src?: string;
-}
+};
async function copyToClipboardWithMeta(value: string, _meta?: Record) {
navigator.clipboard.writeText(value);
}
export const CopyButton = React.forwardRef(
- ({ value, className, src, ...props }, ref) => {
+ ({ value, src, variant = "outline", className, onClick, ...props }, ref) => {
const [copied, setCopied] = React.useState(false);
React.useEffect(() => {
@@ -27,18 +34,24 @@ export const CopyButton = React.forwardRef(
}, [copied]);
return (
-
+
);
},
);
diff --git a/internal/ui/src/index.ts b/internal/ui/src/index.ts
index 1ac2df9016..675bbf53b3 100644
--- a/internal/ui/src/index.ts
+++ b/internal/ui/src/index.ts
@@ -1,5 +1,6 @@
export * from "./components/button";
export * from "./components/card";
+export * from "./components/code";
export * from "./components/copy-button";
export * from "./components/date-time/date-time";
export * from "./components/dialog/dialog-container";