Claude Code Plugins

Community-maintained marketplace

Feedback

Knowledge for implementing the LMS quiz system with Nuxt UI. Activate when working with quiz components, question types, scoring, or quiz-related composables.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name quiz-system
description Knowledge for implementing the LMS quiz system with Nuxt UI. Activate when working with quiz components, question types, scoring, or quiz-related composables.

Quiz System Implementation

Activation Triggers

  • Creating quiz components
  • Working with question types
  • Implementing scoring logic
  • Building QuizContainer, QuizQuestion, QuizResults

TypeScript Interfaces

// app/data/types.ts

export interface QuizQuestion {
  question: string
  type: 'single' | 'multiple' | 'true-false'
  options?: string[]
  correctAnswer?: string | boolean
  correctAnswers?: string[]
  explanation: string
}

export interface Quiz {
  passingScore: number  // Percentage (0-100)
  questions: QuizQuestion[]
}

export interface QuizAnswer {
  questionIndex: number
  selected: string | string[] | boolean
}

export interface QuizState {
  currentIndex: number
  answers: QuizAnswer[]
  isComplete: boolean
  score: number
  passed: boolean
}

Question Types in Markdown

Single Choice

- question: "Which command lists files in Linux?"
  type: single
  options: ["ls", "cd", "pwd", "rm"]
  correctAnswer: "ls"
  explanation: "The 'ls' command lists directory contents."

Multiple Choice

- question: "Which are valid Linux distributions? (Select all)"
  type: multiple
  options: ["Ubuntu", "Windows Server", "Fedora", "macOS", "Debian"]
  correctAnswers: ["Ubuntu", "Fedora", "Debian"]
  explanation: "Ubuntu, Fedora, and Debian are Linux distributions."

True/False

- question: "Git is a distributed version control system."
  type: true-false
  correctAnswer: true
  explanation: "Git is indeed a distributed VCS."

Component Implementation

QuizContainer.vue

<script setup lang="ts">
import type { Quiz } from '~/data/types'

const props = defineProps<{
  quiz: Quiz
}>()

const emit = defineEmits<{
  (e: 'complete', score: number, passed: boolean): void
}>()

const {
  currentQuestion,
  currentIndex,
  totalQuestions,
  isLastQuestion,
  isFirstQuestion,
  isComplete,
  score,
  passed,
  answers,
  submitAnswer,
  nextQuestion,
  previousQuestion,
  finishQuiz,
  getAnswerForQuestion,
  reset
} = useQuiz(props.quiz)

// Track selected answer for current question
const selectedAnswer = ref<string | string[] | boolean | null>(null)

// Initialize selected from previous answers
watch(currentIndex, (newIndex) => {
  const previous = getAnswerForQuestion(newIndex)
  selectedAnswer.value = previous?.selected ?? null
}, { immediate: true })

function handleNext() {
  if (selectedAnswer.value !== null) {
    submitAnswer(selectedAnswer.value)
  }
  
  if (isLastQuestion.value) {
    finishQuiz()
    emit('complete', score.value, passed.value)
  } else {
    nextQuestion()
    selectedAnswer.value = null
  }
}

function handleRetry() {
  reset()
  selectedAnswer.value = null
}
</script>

<template>
  <UCard>
    <!-- Quiz Header -->
    <template #header>
      <div class="flex items-center justify-between">
        <h3 class="font-semibold">Quiz</h3>
        <UBadge variant="soft">
          Question {{ currentIndex + 1 }} of {{ totalQuestions }}
        </UBadge>
      </div>
      <UProgress 
        :value="((currentIndex + 1) / totalQuestions) * 100" 
        class="mt-3"
        size="sm"
      />
    </template>

    <!-- Question (Active Quiz) -->
    <div v-if="!isComplete">
      <QuizQuestion
        :question="currentQuestion"
        v-model="selectedAnswer"
      />
    </div>

    <!-- Results -->
    <QuizResults
      v-else
      :score="score"
      :passed="passed"
      :passing-score="quiz.passingScore"
      :questions="quiz.questions"
      :answers="answers"
    />

    <!-- Footer Actions -->
    <template #footer>
      <div class="flex justify-between">
        <UButton
          v-if="!isComplete"
          variant="outline"
          :disabled="isFirstQuestion"
          @click="previousQuestion"
          class="cursor-pointer"
        >
          <UIcon name="i-heroicons-arrow-left" class="w-4 h-4 mr-2" />
          Previous
        </UButton>
        <div v-else />

        <UButton
          v-if="!isComplete"
          :disabled="selectedAnswer === null"
          @click="handleNext"
          class="cursor-pointer"
        >
          {{ isLastQuestion ? 'Finish Quiz' : 'Next' }}
          <UIcon name="i-heroicons-arrow-right" class="w-4 h-4 ml-2" />
        </UButton>
        
        <UButton
          v-else
          @click="handleRetry"
          class="cursor-pointer"
        >
          <UIcon name="i-heroicons-arrow-path" class="w-4 h-4 mr-2" />
          Try Again
        </UButton>
      </div>
    </template>
  </UCard>
</template>

QuizQuestion.vue

<script setup lang="ts">
import type { QuizQuestion } from '~/data/types'

const props = defineProps<{
  question: QuizQuestion
}>()

const model = defineModel<string | string[] | boolean | null>()

// For multiple choice, manage array
const multipleSelected = computed({
  get: () => (model.value as string[]) || [],
  set: (val) => { model.value = val }
})

function toggleMultiple(option: string) {
  const current = [...multipleSelected.value]
  const index = current.indexOf(option)
  if (index > -1) {
    current.splice(index, 1)
  } else {
    current.push(option)
  }
  multipleSelected.value = current
}

function isSelected(option: string): boolean {
  if (props.question.type === 'multiple') {
    return multipleSelected.value.includes(option)
  }
  return model.value === option
}
</script>

<template>
  <div class="space-y-6">
    <!-- Question Text -->
    <h4 class="text-lg font-medium text-gray-100">
      {{ question.question }}
    </h4>

    <!-- Single Choice Options -->
    <div v-if="question.type === 'single'" class="space-y-3">
      <div
        v-for="option in question.options"
        :key="option"
        class="flex items-center gap-3 p-4 rounded-lg border cursor-pointer transition-all"
        :class="[
          model === option 
            ? 'border-primary-500 bg-primary-500/10' 
            : 'border-gray-700 hover:border-gray-600'
        ]"
        @click="model = option"
      >
        <div 
          class="w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0"
          :class="model === option ? 'border-primary-500' : 'border-gray-600'"
        >
          <div 
            v-if="model === option" 
            class="w-2.5 h-2.5 rounded-full bg-primary-500"
          />
        </div>
        <span class="text-gray-200">{{ option }}</span>
      </div>
    </div>

    <!-- Multiple Choice Options -->
    <div v-else-if="question.type === 'multiple'" class="space-y-3">
      <p class="text-sm text-gray-400">Select all that apply</p>
      <div
        v-for="option in question.options"
        :key="option"
        class="flex items-center gap-3 p-4 rounded-lg border cursor-pointer transition-all"
        :class="[
          isSelected(option)
            ? 'border-primary-500 bg-primary-500/10' 
            : 'border-gray-700 hover:border-gray-600'
        ]"
        @click="toggleMultiple(option)"
      >
        <UCheckbox 
          :model-value="isSelected(option)"
          @update:model-value="toggleMultiple(option)"
        />
        <span class="text-gray-200">{{ option }}</span>
      </div>
    </div>

    <!-- True/False Options -->
    <div v-else-if="question.type === 'true-false'" class="flex gap-4">
      <UButton
        :variant="model === true ? 'solid' : 'outline'"
        :color="model === true ? 'success' : 'secondary'"
        size="lg"
        class="flex-1 cursor-pointer"
        @click="model = true"
      >
        <UIcon name="i-heroicons-check" class="w-5 h-5 mr-2" />
        True
      </UButton>
      <UButton
        :variant="model === false ? 'solid' : 'outline'"
        :color="model === false ? 'error' : 'secondary'"
        size="lg"
        class="flex-1 cursor-pointer"
        @click="model = false"
      >
        <UIcon name="i-heroicons-x-mark" class="w-5 h-5 mr-2" />
        False
      </UButton>
    </div>
  </div>
</template>

QuizResults.vue

<script setup lang="ts">
import type { QuizQuestion, QuizAnswer } from '~/data/types'

const props = defineProps<{
  score: number
  passed: boolean
  passingScore: number
  questions: QuizQuestion[]
  answers: QuizAnswer[]
}>()

function getAnswerStatus(index: number): 'correct' | 'incorrect' | 'unanswered' {
  const answer = props.answers[index]
  const question = props.questions[index]
  
  if (!answer) return 'unanswered'
  
  if (question.type === 'multiple') {
    const selected = answer.selected as string[]
    const correct = question.correctAnswers!
    const isCorrect = selected.length === correct.length && 
      selected.every(s => correct.includes(s))
    return isCorrect ? 'correct' : 'incorrect'
  }
  
  return answer.selected === question.correctAnswer ? 'correct' : 'incorrect'
}
</script>

<template>
  <div class="space-y-6">
    <!-- Score Display -->
    <div class="text-center py-6">
      <div 
        class="inline-flex items-center justify-center w-24 h-24 rounded-full mb-4"
        :class="passed ? 'bg-success-500/20' : 'bg-error-500/20'"
      >
        <span 
          class="text-3xl font-bold"
          :class="passed ? 'text-success-500' : 'text-error-500'"
        >
          {{ score }}%
        </span>
      </div>
      
      <h3 class="text-xl font-semibold mb-2">
        {{ passed ? 'Congratulations!' : 'Keep Learning!' }}
      </h3>
      
      <p class="text-gray-400">
        {{ passed 
          ? 'You passed the quiz!' 
          : `You need ${passingScore}% to pass. Try again!` 
        }}
      </p>
    </div>

    <!-- Question Review -->
    <div class="space-y-4">
      <h4 class="font-medium text-gray-300">Review Your Answers</h4>
      
      <div 
        v-for="(question, index) in questions" 
        :key="index"
        class="p-4 rounded-lg border"
        :class="[
          getAnswerStatus(index) === 'correct' 
            ? 'border-success-500/50 bg-success-500/5' 
            : 'border-error-500/50 bg-error-500/5'
        ]"
      >
        <div class="flex items-start gap-3">
          <UIcon 
            :name="getAnswerStatus(index) === 'correct' 
              ? 'i-heroicons-check-circle-solid' 
              : 'i-heroicons-x-circle-solid'"
            class="w-5 h-5 flex-shrink-0 mt-0.5"
            :class="getAnswerStatus(index) === 'correct' 
              ? 'text-success-500' 
              : 'text-error-500'"
          />
          <div class="flex-1 min-w-0">
            <p class="font-medium text-gray-200 mb-2">{{ question.question }}</p>
            
            <p class="text-sm text-gray-400">
              Your answer: 
              <span :class="getAnswerStatus(index) === 'correct' ? 'text-success-400' : 'text-error-400'">
                {{ answers[index]?.selected ?? 'Not answered' }}
              </span>
            </p>
            
            <p v-if="getAnswerStatus(index) !== 'correct'" class="text-sm text-gray-400">
              Correct answer: 
              <span class="text-success-400">
                {{ question.type === 'multiple' 
                  ? question.correctAnswers?.join(', ') 
                  : question.correctAnswer 
                }}
              </span>
            </p>
            
            <p class="text-sm text-gray-500 mt-2 italic">
              {{ question.explanation }}
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

Scoring Logic

function calculateScore(answers: QuizAnswer[], questions: QuizQuestion[]): number {
  let correctCount = 0
  
  answers.forEach((answer, index) => {
    const question = questions[index]
    if (!answer) return
    
    switch (question.type) {
      case 'single':
        if (answer.selected === question.correctAnswer) {
          correctCount++
        }
        break
        
      case 'multiple':
        const selected = answer.selected as string[]
        const correct = question.correctAnswers!
        // All selected must match exactly
        if (
          selected.length === correct.length &&
          selected.every(s => correct.includes(s))
        ) {
          correctCount++
        }
        break
        
      case 'true-false':
        if (answer.selected === question.correctAnswer) {
          correctCount++
        }
        break
    }
  })
  
  return Math.round((correctCount / questions.length) * 100)
}

Quiz Writing Guidelines

  1. 3-5 questions per lesson - Enough to test understanding without overwhelming
  2. Mix question types - Single choice, multiple choice, true/false
  3. Test understanding, not memorization - "Why" questions, not just "What"
  4. Provide clear explanations - Help learners understand correct answers
  5. 70% passing score - Standard threshold
  6. Avoid trick questions - Be straightforward and fair