diff --git a/frontend/src/components/questions/MultipleChoice.tsx b/frontend/src/components/questions/MultipleChoice.tsx
new file mode 100644
index 0000000..39d7c66
--- /dev/null
+++ b/frontend/src/components/questions/MultipleChoice.tsx
@@ -0,0 +1,40 @@
+import Tex from '../Tex'
+import type { Question } from '../../types'
+
+interface Props {
+ q: Question
+ value: string | null | undefined
+ onChange: (val: string) => void
+ locked?: boolean
+ graded?: boolean
+}
+
+export default function MultipleChoice({ q, value, onChange, locked, graded }: Props) {
+ return (
+
+ {(q.choices ?? []).map((c, i) => {
+ const isSelected = value === c.id
+ let cls = 'choice'
+ if (isSelected) cls += ' selected'
+ if (graded) {
+ if (c.id === q.answer) cls += ' correct'
+ else if (isSelected) cls += ' wrong'
+ }
+ return (
+
+ )
+ })}
+
+ )
+}
diff --git a/frontend/src/components/questions/MultipleSelect.tsx b/frontend/src/components/questions/MultipleSelect.tsx
new file mode 100644
index 0000000..d9046f8
--- /dev/null
+++ b/frontend/src/components/questions/MultipleSelect.tsx
@@ -0,0 +1,40 @@
+import Tex from '../Tex'
+import type { Question } from '../../types'
+
+interface Props {
+ q: Question
+ value: string[] | null | undefined
+ onChange: (val: string[]) => void
+ locked?: boolean
+ graded?: boolean
+}
+
+export default function MultipleSelect({ q, value, onChange, locked, graded }: Props) {
+ const current = value ?? []
+ const toggle = (id: string) => {
+ if (locked) return
+ onChange(current.includes(id) ? current.filter((v) => v !== id) : [...current, id])
+ }
+ const answerSet = new Set((q.answer as string[]) ?? [])
+
+ return (
+
+ {(q.choices ?? []).map((c, i) => {
+ const isSelected = current.includes(c.id)
+ let cls = 'choice'
+ if (isSelected) cls += ' selected'
+ if (graded) {
+ if (answerSet.has(c.id)) cls += ' correct'
+ else if (isSelected) cls += ' wrong'
+ }
+ return (
+
+ )
+ })}
+
+ )
+}