Skip to content

Inline Components

Embed custom Vue components directly into your markdown card content using {{ <component-name /> }} syntax. This is a middle ground between basic fill-in syntax and fully custom cards.

Basic Example

The simplest inline component takes no parameters:

Markdown Source:

Rendered Card:

Check out this NEW feature!

Here, badge is a Vue component that renders a styled span. The syntax {{ <badge /> }} tells the markdown renderer to insert the component inline.

Components with Data

Pass data to components using attributes:

Markdown Source:

Rendered Card:

Status: ALERT

Priority: HIGH

The color and text attributes become props on the Vue component. This lets you reuse the same component with different data across many cards.

Real-World Example: Chess Positions

A chess position component that accepts FEN notation:

Markdown Source:

Rendered Card:

White to move. What's the best continuation?

The answer is .

Notice how the chess component and fill-in blank {{ Nxe5 }} coexist in the same card. Multiple props work too:

Markdown Source:

Rendered Card:

Small board:

Large board:

Setting Up Components

Register components when bootstrapping your Vue app (in src/main.ts):

typescript
import { markRaw } from 'vue';
import Badge from './components/Badge.vue';
import ChessPosition from './components/ChessPosition.vue';

app.provide('markdownComponents', {
  badge: markRaw(Badge),
  chessPosition: markRaw(ChessPosition),
});

Why markRaw()?

Use markRaw() for performance - it prevents Vue from making components reactive, which isn't needed here.

Components can be defined inline (like in this page's examples) or imported from .vue files. The markdown syntax stays the same either way.

Interactive Components (Grading)

Components can participate in answer evaluation by extending BaseUserInput:

vue
<script lang="ts">
import BaseUserInput from '@vue-skuilder/common-ui/components/studentInputs/BaseUserInput';

export default {
  extends: BaseUserInput,
  props: ['expectedAnswer'],
  methods: {
    handleSubmit(value) {
      // When user provides answer, submit for grading
      this.submit({ userValue: value });
    }
  }
}
</script>

How grading works:

  1. Component calls this.submit(answer) with user's response
  2. BaseUserInput walks up the component tree to find QuestionView ancestor
  3. QuestionView calls Question.evaluate(answer, timeSpent)
  4. Returns Evaluation: { isCorrect: boolean, performance: number (0-1) }

See the fill-in implementation for a complete example:

  • Component: packages/common-ui/src/components/studentInputs/fillInInput.vue
  • Question class: packages/courseware/src/default/questions/fillIn/index.ts

Key types (from @vue-skuilder/common):

  • Answer - Base interface for user responses
  • Evaluation - { isCorrect: boolean, performance: number }

Key classes (from @vue-skuilder/common-ui):

  • BaseUserInput - Base component with submit() method
  • Question - Abstract class with isCorrect() and evaluate() methods

When to Use What?

  • {{ }} fill-in syntax → Simple text input or multiple choice
  • Inline components (this page) → Custom UI reused across many cards
  • Full custom cards → Complete control over card logic, data model, and views