Learning Objectives
Lists learning goals for educational content. Install Learning Objectives from the VLLNT UI registry with the shadcn CLI.
Lists learning goals for educational content. Part of the learning family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.
Preview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
Add Learning Objectives to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:
pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/learning-objectives.jsonSource
"use client";
import { CheckCircle2, Clock, GraduationCap, Target } from "lucide-react";
import type { ReactNode } from "react";
import type { HeadingTag } from "../../lib/types";
export type LearningObjectivesProps = {
/** Heading tag for the title. Defaults to `h4`. */
as?: HeadingTag;
estimatedTime?: string;
objectives: string[];
title?: string;
};
export function LearningObjectives({
as: Heading = "h4",
estimatedTime,
objectives,
title = "What you'll learn",
}: LearningObjectivesProps) {
return (
<div className="my-6 rounded-lg border bg-gradient-to-br from-primary/5 to-primary/10 p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Target className="size-5 text-primary" />
<Heading className="font-semibold text-foreground">{title}</Heading>
</div>
{estimatedTime ? (
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Clock className="size-4" />
<span>{estimatedTime}</span>
</div>
) : null}
</div>
<ul className="space-y-2">
{objectives.map((objective) => (
<li className="flex items-start gap-2" key={objective}>
<CheckCircle2 className="size-4 text-primary flex-shrink-0 mt-0.5" />
<span className="text-sm text-muted-foreground">{objective}</span>
</li>
))}
</ul>
</div>
);
}
export type PrerequisitesProps = {
items: string[];
level?: "advanced" | "beginner" | "intermediate";
title?: string;
};
export function Prerequisites({
items,
level,
title = "Prerequisites",
}: PrerequisitesProps) {
return (
<div className="my-6 rounded-lg border bg-muted/30 p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<GraduationCap className="size-5 text-muted-foreground" />
<h4 className="font-semibold text-foreground">{title}</h4>
</div>
{level ? (
<span className="text-xs font-medium px-2 py-1 rounded-full bg-primary/10 text-primary capitalize">
{level}
</span>
) : null}
</div>
<ul className="space-y-2">
{items.map((item) => (
<li
className="flex items-start gap-2 text-sm text-muted-foreground"
key={item}
>
<span className="text-primary">•</span>
{item}
</li>
))}
</ul>
</div>
);
}
export type SummaryProps = {
children: ReactNode;
keyTakeaways?: string[];
title?: string;
};
export function Summary({
children,
keyTakeaways,
title = "Summary",
}: SummaryProps) {
return (
<div className="my-6 rounded-lg border bg-muted/30 p-6">
<h4 className="font-semibold text-foreground mb-3 flex items-center gap-2">
<GraduationCap className="size-5" />
{title}
</h4>
<div className="text-sm text-muted-foreground [&>p]:mb-2">{children}</div>
{keyTakeaways && keyTakeaways.length > 0 ? (
<div className="mt-4 pt-4 border-t border-border">
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2">
Key Takeaways
</p>
<ul className="space-y-1">
{keyTakeaways.map((takeaway) => (
<li className="flex items-start gap-2 text-sm" key={takeaway}>
<CheckCircle2 className="size-4 text-green-500 flex-shrink-0 mt-0.5" />
<span className="text-muted-foreground">{takeaway}</span>
</li>
))}
</ul>
</div>
) : null}
</div>
);
}
Stories
Explore every variant and state in the interactive Storybook:
Preview
Switch between light and dark to inspect the embedded Storybook preview.