The demo below shows a goal sequent at the bottom. Click Apply rule to break it into sub-goals using the matching left or right rule. Keep applying rules until every open goal becomes an axiom (A⊢A). The full proof tree grows upward.
<!-- Sequent calculus proof builder — derive (A & B) -> A step by step -->
<div class="sc-app">
<div class="goal-label">Goal sequent:</div>
<div id="goal-display" class="sequent goal-sequent"></div>
<div id="proof-tree" class="proof-tree"></div>
<div id="status" class="status"></div>
<div class="btns">
<button id="btn-apply" type="button">Apply rule</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<details class="rules-ref">
<summary>LK rule reference</summary>
<ul class="rule-list">
<li><code>Ax</code> — if A appears on both sides: A, Γ ⊢ Δ, A</li>
<li><code>&R</code> — split conjunction right: Γ ⊢ A and Γ ⊢ B gives Γ ⊢ A ∧ B</li>
<li><code>&L</code> — use conjunction left: A, B, Γ ⊢ Δ gives A ∧ B, Γ ⊢ Δ</li>
<li><code>→R</code> — introduce implication right: A, Γ ⊢ B gives Γ ⊢ A → B</li>
<li><code>→L</code> — use implication left: Γ ⊢ A and B, Γ ⊢ Δ gives A→B, Γ ⊢ Δ</li>
<li><code>∨R</code> — introduce disjunction right: Γ ⊢ A, B gives Γ ⊢ A ∨ B</li>
</ul>
</details>
</div>
/* Styles for the sequent calculus interactive proof builder */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; font-size: 14px; }
.sc-app { padding: .5rem; }
.goal-label { font-size: .8rem; font-weight: 700; text-transform: uppercase;
letter-spacing: .06em; color: #666; margin-bottom: .3rem; }
.sequent { font-family: ui-monospace, monospace; font-size: 1rem; padding: .35rem .6rem;
background: #eef2f7; border: 1px solid #c8d4e0; border-radius: 6px;
display: inline-block; margin-bottom: .5rem; }
.goal-sequent { background: #d4e8ff; border-color: #6aabf0; font-weight: 700; }
.proof-tree { margin: .5rem 0; min-height: 1.5rem; }
.node { margin-bottom: .4rem; }
.node-seq { font-family: ui-monospace, monospace; font-size: .9rem;
padding: .25rem .5rem; border-radius: 6px; display: inline-block; }
.node-seq.open { background: #fff4cc; border: 1px solid #e0c840; }
.node-seq.axiom { background: #d6f5d6; border: 1px solid #72cc72; }
.node-seq.derived { background: #eef2f7; border: 1px solid #c8d4e0; color: #666; }
.rule-tag { font-size: .75rem; color: #888; margin-left: .4rem; }
.indent { margin-left: 1.4rem; border-left: 2px solid #d0d8e4; padding-left: .6rem; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .4rem 0; }
.status.ok { color: #0a7d33; }
.status.hint { color: #1d3557; }
.status.done { color: #6a0dad; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
.rules-ref { margin-top: .8rem; font-size: .82rem; color: #444; }
.rules-ref summary { cursor: pointer; font-weight: 600; }
.rule-list { margin: .4rem 0 0 1rem; padding: 0; list-style: disc; }
.rule-list li { margin: .15rem 0; }
// Sequent calculus demo — step-by-step LK derivation of (A & B) -> A
// The goal is the tautology (A & B) -> A, proved in 3 rule steps
// Goal tautology: (A & B) -> A
// Proof tree (bottom-up, LK rules):
// ->R: (A & B) -> A becomes A & B |- A
// &L: A & B |- A becomes A, B |- A
// Ax: A, B |- A (axiom — A appears on both sides)
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Ordered steps: index 0 = initial goal (not yet applied), 1..3 = rule applications
const STEPS = [
{
rule: null,
seq: '⊢ (A ∧ B) → A',
note: 'Initial goal',
children: []
},
{
rule: '→R',
seq: 'A ∧ B ⊢ A',
note: 'Apply →R: move antecedent to the left',
children: []
},
{
rule: '∧L',
seq: 'A, B ⊢ A',
note: 'Apply ∧L: split the conjunction on the left',
children: []
},
{
rule: 'Ax',
seq: 'A, B ⊢ A',
note: 'Axiom: A appears on both sides',
children: [],
isAxiom: true
}
];
let stepIdx = 0;
// lines accumulated for the growing proof tree
let treeLines = [];
function seqClass(idx) {
if (idx < stepIdx) {
if (STEPS[idx].isAxiom) return 'axiom';
return 'derived';
}
return 'open';
}
function renderTree() {
const container = document.getElementById('proof-tree');
container.innerHTML = '';
if (stepIdx === 0) return;
// Show the derivation chain from current top down to the original goal.
// Show the derivation chain from current top down to the original goal.
for (let i = stepIdx; i >= 1; i--) {
const step = STEPS[i];
const div = document.createElement('div');
div.className = 'node';
const indent = document.createElement('div');
indent.className = 'indent';
const seqDiv = document.createElement('div');
const cls = step.isAxiom && i < stepIdx ? 'axiom' : (i === stepIdx ? 'open' : 'derived');
seqDiv.className = 'node-seq ' + cls;
seqDiv.textContent = step.seq;
const tag = document.createElement('span');
tag.className = 'rule-tag';
tag.textContent = '[' + step.rule + ']';
indent.appendChild(seqDiv);
indent.appendChild(tag);
div.appendChild(indent);
container.prepend(div);
}
}
function setStatus(msg, cls) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = 'status ' + cls;
}
function applyStep() {
if (stepIdx >= STEPS.length - 1) return;
stepIdx++;
renderTree();
const step = STEPS[stepIdx];
if (step.isAxiom) {
setStatus(i18nFmt('Axiom reached: %seq%', { seq: step.seq }), 'ok');
document.getElementById('btn-apply').disabled = true;
setStatus('Proof complete! Every goal reduced to an axiom.', 'done');
} else {
setStatus(i18nFmt('Applied [%rule%] — new goal: %seq%', { rule: step.rule, seq: step.seq }), 'hint');
}
}
function reset() {
stepIdx = 0;
treeLines = [];
renderTree();
document.getElementById('btn-apply').disabled = false;
setStatus('Click "Apply rule" to begin the derivation.', 'hint');
const goalEl = document.getElementById('goal-display');
goalEl.textContent = STEPS[0].seq;
}
document.getElementById('btn-apply').addEventListener('click', applyStep);
document.getElementById('btn-reset').addEventListener('click', reset);
// Initialize the demo
reset();
<!-- Sequent calculus proof builder — derive (A & B) -> A step by step -->
<div class="sc-app">
<div class="goal-label">Goal sequent:</div>
<div id="goal-display" class="sequent goal-sequent"></div>
<div id="proof-tree" class="proof-tree"></div>
<div id="status" class="status"></div>
<div class="btns">
<button id="btn-apply" type="button">Apply rule</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<details class="rules-ref">
<summary>LK rule reference</summary>
<ul class="rule-list">
<li><code>Ax</code> — if A appears on both sides: A, Γ ⊢ Δ, A</li>
<li><code>&R</code> — split conjunction right: Γ ⊢ A and Γ ⊢ B gives Γ ⊢ A ∧ B</li>
<li><code>&L</code> — use conjunction left: A, B, Γ ⊢ Δ gives A ∧ B, Γ ⊢ Δ</li>
<li><code>→R</code> — introduce implication right: A, Γ ⊢ B gives Γ ⊢ A → B</li>
<li><code>→L</code> — use implication left: Γ ⊢ A and B, Γ ⊢ Δ gives A→B, Γ ⊢ Δ</li>
<li><code>∨R</code> — introduce disjunction right: Γ ⊢ A, B gives Γ ⊢ A ∨ B</li>
</ul>
</details>
</div>
/* Styles for the sequent calculus interactive proof builder */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; font-size: 14px; }
.sc-app { padding: .5rem; }
.goal-label { font-size: .8rem; font-weight: 700; text-transform: uppercase;
letter-spacing: .06em; color: #666; margin-bottom: .3rem; }
.sequent { font-family: ui-monospace, monospace; font-size: 1rem; padding: .35rem .6rem;
background: #eef2f7; border: 1px solid #c8d4e0; border-radius: 6px;
display: inline-block; margin-bottom: .5rem; }
.goal-sequent { background: #d4e8ff; border-color: #6aabf0; font-weight: 700; }
.proof-tree { margin: .5rem 0; min-height: 1.5rem; }
.node { margin-bottom: .4rem; }
.node-seq { font-family: ui-monospace, monospace; font-size: .9rem;
padding: .25rem .5rem; border-radius: 6px; display: inline-block; }
.node-seq.open { background: #fff4cc; border: 1px solid #e0c840; }
.node-seq.axiom { background: #d6f5d6; border: 1px solid #72cc72; }
.node-seq.derived { background: #eef2f7; border: 1px solid #c8d4e0; color: #666; }
.rule-tag { font-size: .75rem; color: #888; margin-left: .4rem; }
.indent { margin-left: 1.4rem; border-left: 2px solid #d0d8e4; padding-left: .6rem; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .4rem 0; }
.status.ok { color: #0a7d33; }
.status.hint { color: #1d3557; }
.status.done { color: #6a0dad; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
.rules-ref { margin-top: .8rem; font-size: .82rem; color: #444; }
.rules-ref summary { cursor: pointer; font-weight: 600; }
.rule-list { margin: .4rem 0 0 1rem; padding: 0; list-style: disc; }
.rule-list li { margin: .15rem 0; }
// Sequent calculus demo — step-by-step LK derivation of (A & B) -> A
// The goal is the tautology (A & B) -> A, proved in 3 rule steps
// Goal tautology: (A & B) -> A
// Proof tree (bottom-up, LK rules):
// ->R: (A & B) -> A becomes A & B |- A
// &L: A & B |- A becomes A, B |- A
// Ax: A, B |- A (axiom — A appears on both sides)
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Ordered steps: index 0 = initial goal (not yet applied), 1..3 = rule applications
const STEPS = [
{
rule: null,
seq: '⊢ (A ∧ B) → A',
note: 'Initial goal',
children: []
},
{
rule: '→R',
seq: 'A ∧ B ⊢ A',
note: 'Apply →R: move antecedent to the left',
children: []
},
{
rule: '∧L',
seq: 'A, B ⊢ A',
note: 'Apply ∧L: split the conjunction on the left',
children: []
},
{
rule: 'Ax',
seq: 'A, B ⊢ A',
note: 'Axiom: A appears on both sides',
children: [],
isAxiom: true
}
];
let stepIdx = 0;
// lines accumulated for the growing proof tree
let treeLines = [];
function seqClass(idx) {
if (idx < stepIdx) {
if (STEPS[idx].isAxiom) return 'axiom';
return 'derived';
}
return 'open';
}
function renderTree() {
const container = document.getElementById('proof-tree');
container.innerHTML = '';
if (stepIdx === 0) return;
// Show the derivation chain from current top down to the original goal.
// Show the derivation chain from current top down to the original goal.
for (let i = stepIdx; i >= 1; i--) {
const step = STEPS[i];
const div = document.createElement('div');
div.className = 'node';
const indent = document.createElement('div');
indent.className = 'indent';
const seqDiv = document.createElement('div');
const cls = step.isAxiom && i < stepIdx ? 'axiom' : (i === stepIdx ? 'open' : 'derived');
seqDiv.className = 'node-seq ' + cls;
seqDiv.textContent = step.seq;
const tag = document.createElement('span');
tag.className = 'rule-tag';
tag.textContent = '[' + step.rule + ']';
indent.appendChild(seqDiv);
indent.appendChild(tag);
div.appendChild(indent);
container.prepend(div);
}
}
function setStatus(msg, cls) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = 'status ' + cls;
}
function applyStep() {
if (stepIdx >= STEPS.length - 1) return;
stepIdx++;
renderTree();
const step = STEPS[stepIdx];
if (step.isAxiom) {
setStatus(i18nFmt('Axiom reached: %seq%', { seq: step.seq }), 'ok');
document.getElementById('btn-apply').disabled = true;
setStatus('Proof complete! Every goal reduced to an axiom.', 'done');
} else {
setStatus(i18nFmt('Applied [%rule%] — new goal: %seq%', { rule: step.rule, seq: step.seq }), 'hint');
}
}
function reset() {
stepIdx = 0;
treeLines = [];
renderTree();
document.getElementById('btn-apply').disabled = false;
setStatus('Click "Apply rule" to begin the derivation.', 'hint');
const goalEl = document.getElementById('goal-display');
goalEl.textContent = STEPS[0].seq;
}
document.getElementById('btn-apply').addEventListener('click', applyStep);
document.getElementById('btn-reset').addEventListener('click', reset);
// Initialize the demo
reset();
Notice what just happened: you derived a tautology without any creativity — just a fixed menu of rules applied in the right order. That is the core promise of sequent calculus. Checking the finished tree takes a single pass; finding which rules to apply and in which order is where the real work hides.
Comments
Loading comments...