Code Collapse
A collapsible code block wrapper, perfect for showing long code examples without overwhelming your documentation.
Overview
The ProseCodeCollapse component wraps code blocks and provides a toggle button to expand or collapse content. It's ideal for lengthy code examples that would take up too much vertical space, starting at a fixed height with a gradient fade effect.
- Clean Toggle - Simple expand/collapse functionality
- Gradient Fade - Visual indicator when content is collapsed
- Customizable Labels - Configure button text and labels
- Accessible - Proper ARIA attributes for screen readers
- Smart Defaults - Works great out of the box with sensible defaults
Basic Usage
Wrap any code block to make it collapsible:
Mermaid Plugin
import mermaid from "mermaid";
import type { MermaidConfig } from "mermaid";
export default defineNuxtPlugin(() => {
/**
* Mermaid initialization configuration
*/
const mermaidInitConfig = {
startOnLoad: false,
themeVariables: {
fontFamily: "var(--font-sans)",
fontSize: "13px",
},
flowchart: {
curve: "basis",
useMaxWidth: true,
},
sequence: {
actorMargin: 50,
showSequenceNumbers: false,
},
suppressErrorRendering: true,
} as MermaidConfig;
/**
* Initialize Mermaid with the specified configuration
*/
mermaid.initialize(mermaidInitConfig);
return {
provide: {
mermaidInstance: mermaid,
mermaidInitConfig,
},
};
});
Custom Labels
Customize the button text and name:
<script setup lang="ts">
import { computed, ref } from "vue";
import type { User } from "@/types";
const users = ref<User[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
const activeUsers = computed(() => users.value.filter((u) => u.role !== "guest"));
const totalUsers = computed(() => users.value.length);
async function fetchUsers() {
loading.value = true;
error.value = null;
try {
const response = await fetch("/api/users");
if (!response.ok) throw new Error("Failed to fetch");
users.value = await response.json();
} catch (e) {
error.value = e instanceof Error ? e.message : "Unknown error";
} finally {
loading.value = false;
}
}
async function deleteUser(id: string) {
try {
await fetch(`/api/users/${id}`, { method: "DELETE" });
users.value = users.value.filter((u) => u.id !== id);
} catch (e) {
console.error("Delete failed:", e);
}
}
onMounted(() => {
fetchUsers();
});
</script>
<template>
<div class="user-manager">
<h2>Users ({{ totalUsers }})</h2>
<div v-if="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else class="user-grid">
<UserCard v-for="user in activeUsers" :key="user.id" :user="user" @delete="deleteUser" />
</div>
</div>
</template>
With Code Snippet
Combine with ::prose-code-snippet to show actual source files:
Use Form Field Composable
import { FORM_ITEM_INJECTION_KEY } from "@/components/Ui/Form/Item.vue";
import {
FieldContextKey,
useFieldError,
useIsFieldDirty,
useIsFieldTouched,
useIsFieldValid,
} from "vee-validate";
import { inject } from "vue";
export function useFormField() {
const fieldContext = inject(FieldContextKey);
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY);
const fieldState = {
valid: useIsFieldValid(),
isDirty: useIsFieldDirty(),
isTouched: useIsFieldTouched(),
error: useFieldError(),
};
if (!fieldContext) throw new Error("useFormField should be used within <FormField>");
const { name } = fieldContext;
const id = fieldItemContext;
return {
id,
name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
}
Custom Icon
Change the toggle icon:
Chip Component
<template>
<div data-slot="chip" class="relative inline-flex shrink-0 items-center justify-center">
<slot />
<span
v-if="localModel"
:class="[styles({ position, size, inset, class: [props.color, props.class] })]"
>
<slot name="content">
{{ text }}
</slot>
</span>
</div>
</template>
<script lang="ts" setup>
import type { HTMLAttributes } from "vue";
defineOptions({ inheritAttrs: false });
const props = withDefaults(
defineProps<{
/**
* The text to display in the chip.
*
* Can be overridden by the `content` slot.
*/
text?: string;
/**
* The color of the chip.
*
* @default `bg-primary`
*/
color?: string;
/**
* The size of the chip.
*
* @default `sm`
*/
size?: VariantProps<typeof styles>["size"];
/**
* The position of the chip.
*
* @default `top-right`
*/
position?: VariantProps<typeof styles>["position"];
/**
* Whether the chip should be inset.
*
* @default `false`
*/
inset?: boolean;
/**
* Whether the chip should be visible.
*
* Can be used with `v-model` to control visibility.
*
* @default `true`
*/
show?: boolean;
/**
* Additional classes to apply to the chip.
*/
class?: HTMLAttributes["class"];
}>(),
{ show: true, color: "bg-primary", inset: false }
);
const localModel = defineModel<boolean>("show", { default: true });
const styles = tv({
base: "absolute flex items-center justify-center rounded-full font-medium whitespace-nowrap text-foreground ring-[2px] ring-background",
variants: {
position: {
"top-right": "top-0 right-0",
"bottom-right": "right-0 bottom-0",
"top-left": "top-0 left-0",
"bottom-left": "bottom-0 left-0",
},
inset: {
true: "",
false: "",
},
size: {
"3xs": "h-[4px] min-w-[4px] p-px text-[4px]",
"2xs": "h-[5px] min-w-[5px] p-px text-[5px]",
xs: "h-1.5 min-w-[0.375rem] p-px text-[6px]",
sm: "h-2 min-w-[0.5rem] p-0.5 text-[7px]",
md: "h-2.5 min-w-2.5 p-0.5 text-[8px]",
lg: "h-3 min-w-[0.75rem] p-0.5 text-[10px]",
xl: "h-3.5 min-w-[0.875rem] p-1 text-[11px]",
"2xl": "h-4 min-w-[1rem] p-1 text-[12px]",
"3xl": "h-5 min-w-[1.25rem] p-1 text-[14px]",
},
},
defaultVariants: {
size: "sm",
position: "top-right",
inset: false,
},
compoundVariants: [
{
inset: false,
position: "top-right",
class: "translate-x-1/2 -translate-y-1/2 transform",
},
{
inset: false,
position: "bottom-right",
class: "-translate-x-1/2 translate-y-1/2 transform",
},
{
inset: false,
position: "top-left",
class: "-translate-x-1/2 -translate-y-1/2 transform",
},
{
inset: false,
position: "bottom-left",
class: "-translate-x-1/2 translate-y-1/2 transform",
},
],
});
</script>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon | string | "lucide:chevron-down" | Icon displayed on the toggle button |
name | string | "Code" | Name/label shown in the button text |
openText | string | "Expand" | Text shown when code is collapsed (clickable to expand) |
closeText | string | "Collapse" | Text shown when code is expanded (clickable to collapse) |
class | string | - | Additional CSS classes for the root container |
Behavior
Initial State
- Code starts collapsed at 200px height
- Gradient fade overlay indicates more content below
- Button shows: "{openText} {name}" (e.g., "Expand Code")
Expanded State
- Code expands to full height (max 80vh)
- Gradient fade removed
- Button shows: "{closeText} {name}" (e.g., "Collapse Code")
Accessibility
The component includes proper accessibility features:
- ARIA Attributes:
aria-expandedindicates current state - ARIA Labels:
aria-labelprovides context for screen readers - Keyboard Support: Button is focusable and clickable via keyboard
- Icon Decoration: Chevron icon is marked
aria-hidden="true" - Semantic HTML: Uses proper button element for interaction
Use Cases
- Long Examples - Code snippets over ~30 lines
- Complete Files - Full component implementations
- Optional Details - Advanced examples users can expand if needed
- Tutorial Code - Progressive disclosure of complex solutions
- API References - Full endpoint implementations
Best Practices
- Use Descriptive Names - Make it clear what's being collapsed
::prose-code-collapse{name="Full Implementation"} - Customize Button Text - Match your documentation tone
::prose-code-collapse{openText="Show" closeText="Hide"} - Combine with Snippets - Keep docs in sync with source
::prose-code-collapse ::prose-code-snippet{file="/path/to/file.vue"} :: :: - Use Sparingly - Don't collapse everything; save for truly long code
- Consider Context - Some readers want full code upfront, others prefer collapsed
Related Components
ProseCodeSnippet - Import code from files or URL
ProseCodeGroup - Tabbed code snippets
ProseCodeTree - Interactive file tree with code preview