Back to index

Permission Matrix

Table
Generates documents
Permission Matrix

Seen as a design

Take it with you

Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.

Everything

permission-matrix-everything.md

Everything in this panel, including the contents behind each URL, in one Markdown file

Prompt

1.4 KB
Build an HTML document with "Permission Matrix". This layout runs, so there is no composition to rebuild — what it needs is content.

## What this layout does
Permission Matrix with explicit source values and reading conditions.

## What you have to supply
- Provide the source values, labels and decision conditions described by the schema.

## The shape of the input
- `title` — string — 結論を伝える見出し
- `subtitle` — string? — 出典・時点・母集団や仮定
- `roles` — array (0, 1, 2, 3) — 役割の名称
- `actions` — array (label, permissions, condition) — 操作と役割順の権限、適用条件

schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing.

## When not to use it
- Do not compare values with incompatible units, populations, or assumptions.

## How to build it
Put the input in a JSON file and run:

```bash
node scripts/render.mjs permission-matrix <input.json> --theme ink -o out.html
```

The output is one self-contained file and fetches nothing at render time.

## The source
- https://design-vault.pages.dev/designs/permission-matrix/source/template.tsx
- https://design-vault.pages.dev/designs/permission-matrix/source/schema.ts
- https://design-vault.pages.dev/designs/permission-matrix/source/sample-data.json
- https://design-vault.pages.dev/designs/permission-matrix/sample.html

Files

Template, input schema, sample data and metadata. This document was generated from those four. The generated document

template.tsx

6.7 KB

/designs/permission-matrix/source/template.tsx

Show contents
import type { ReactNode } from "react";
import type { Props } from "./schema";
function Ledger({heads,rows,caption}:{heads:string[];rows:ReactNode[][];caption?:string}) { return <table className="ledger">{caption && <caption>{caption}</caption>}<thead><tr>{heads.map((h,i)=><th key={i} scope="col">{h}</th>)}</tr></thead><tbody>{rows.map((r,i)=><tr key={i}>{r.map((v,j)=><td key={j} data-label={heads[j]}>{v}</td>)}</tr>)}</tbody></table>; }
function Notes({children}:{children:ReactNode}) { return <details><summary>データ・判断条件を確認</summary>{children}</details>; }

const CSS = String.raw`
.asset{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset *{box-sizing:border-box}.asset header{margin-bottom:2rem}.asset .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);letter-spacing:.08em;color:var(--color-secondary);margin-bottom:.75rem}.asset h1{font-size:var(--text-h1);font-weight:700;line-height:1.35;letter-spacing:-.025em;max-width:32em}.asset h2{font-size:var(--text-h3);font-weight:600;margin-bottom:.75rem}.asset h3{font-size:var(--text-body-md);font-weight:600}.asset .subtitle,.asset .muted{color:var(--color-secondary)}.asset .subtitle{margin-top:.75rem}.asset .number{font-variant-numeric:tabular-nums;font-family:var(--font-mono)}.asset .hero-number{font-size:var(--text-display);line-height:1.2;font-weight:700;letter-spacing:-.04em}.asset .callout{border-left:.25rem solid var(--color-tertiary);padding:1rem 1.5rem;background:var(--color-surface-subtle);margin-bottom:1.5rem}.asset .grid2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}.asset .grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.5rem}.asset .panel{padding:1.5rem;background:var(--color-surface-subtle);border-radius:var(--radius-md);min-width:0}.asset .rule{border-top:1px solid var(--color-outline);padding-top:1rem;margin-top:1rem}.asset .pill{display:inline-block;padding:.25rem .75rem;border:1px solid var(--color-outline);border-radius:var(--radius-full);font-size:var(--text-label)}.asset .focus{background:var(--color-tertiary-container);color:var(--color-on-tertiary-container)}.asset .dark{background:var(--color-primary);color:var(--color-on-primary)}.asset .dark .muted{color:var(--color-on-primary-container)}.asset .row{display:grid;grid-template-columns:12rem minmax(0,1fr) 6rem;gap:1rem;align-items:center;padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .row>*{min-width:0}.asset .track{height:1rem;position:relative;background:var(--color-neutral);border-radius:var(--radius-sm)}.asset .bar{height:100%;background:var(--color-tertiary);border-radius:var(--radius-sm)}.asset .track.tall{height:2rem}.asset .flex{display:flex;gap:1rem;align-items:center;justify-content:space-between;flex-wrap:wrap}.asset .stack>*+*{margin-top:1rem}.asset ul{list-style:disc;padding-left:1.25rem}.asset li+li{margin-top:.5rem}.asset .plot{display:block;width:100%;height:auto;overflow:visible}.asset .plot text{font-family:var(--font-sans);font-size:14px;fill:var(--color-secondary)}.asset .plot .ink{fill:var(--color-primary)}.asset .plot .axis{stroke:var(--color-outline);stroke-width:1}.asset .plot .accent{stroke:var(--color-tertiary);fill:none;stroke-width:3}.asset .legend{display:flex;gap:1.5rem;flex-wrap:wrap;font-size:var(--text-label);margin-bottom:1rem}.asset .legend b{display:inline-block;width:.75rem;height:.75rem;margin-right:.5rem}.asset .ledger{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}.asset .ledger caption{text-align:left;font-weight:600;margin:1rem 0}.asset .ledger th{font-weight:600;color:var(--color-secondary);text-align:left}.asset .ledger th,.asset .ledger td{padding:.75rem 1rem;border-bottom:1px solid var(--color-outline);vertical-align:top;overflow-wrap:anywhere}.asset .ledger th:first-child,.asset .ledger td:first-child{padding-left:0}.asset .ledger th:last-child,.asset .ledger td:last-child{padding-right:0}.asset .ledger tbody tr:hover{background:var(--color-surface-subtle)}.asset details{border-top:1px solid var(--color-outline);margin-top:1.5rem;padding-top:1rem}.asset summary{cursor:pointer;color:var(--color-secondary);padding:.5rem 0;min-height:2.75rem}.asset summary:focus-visible{outline:2px solid var(--color-tertiary);outline-offset:.25rem}.asset .small{font-size:var(--text-label)}.asset .mobile-only{display:none}.asset .horizontal-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.asset .board-column{border-top:.25rem solid var(--color-primary);padding-top:1rem;min-width:0}.asset .board-item{padding:1rem 0;border-bottom:1px solid var(--color-outline)}.asset .board-item p{margin-top:.5rem}.asset .statement{font-size:var(--text-h2);line-height:1.5;font-weight:600}.asset .meta-line{margin-top:.75rem;font-size:var(--text-label);color:var(--color-secondary)}.asset pre{white-space:pre-wrap;overflow-wrap:anywhere}.asset footer{margin-top:2rem;font-size:var(--text-label);color:var(--color-secondary)}
@media(max-width:640px){.asset{padding:1.5rem 1rem}.asset h1{font-size:var(--text-h2)}.asset header{margin-bottom:1.5rem}.asset .grid2,.asset .grid3,.asset .horizontal-list{grid-template-columns:1fr}.asset .row{grid-template-columns:minmax(0,1fr) 5rem;gap:.5rem}.asset .row>:first-child{grid-column:1/-1}.asset .desktop-only{display:none!important}.asset .mobile-only{display:block}.asset .panel{padding:1rem}.asset .ledger,.asset .ledger tbody,.asset .ledger tr,.asset .ledger td{display:block}.asset .ledger thead{display:none}.asset .ledger tr{padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .ledger td{display:grid;grid-template-columns:7rem minmax(0,1fr);gap:.75rem;border:0;padding:.25rem 0!important}.asset .ledger td:before{content:attr(data-label);color:var(--color-secondary);font-size:var(--text-label)}.asset .hero-number{font-size:var(--text-h1)}}
@media print{.asset{max-width:none;padding:1rem}.asset details>*{display:block}.asset summary{display:none}}
`;

export default function Template(d: Props) {
const names={allow:"許可",deny:"不可",approval:"承認要"};
return <section className="asset asset-permission-matrix"><style>{CSS}</style><header><p className="eyebrow">PERMISSION MATRIX</p><h1>{d.title}</h1>{d.subtitle && <p className="subtitle">{d.subtitle}</p>}</header>
<Ledger heads={["操作",...d.roles,"条件"]} rows={d.actions.map(a=>[a.label,...a.permissions.map((p,i)=><span className={p==="approval"?"pill focus":"pill"} key={i}>{names[p]}</span>),a.condition])}/><Notes><p>権限を設定・変更する画面ではありません。「承認要」を「許可」と同一視しないための仕様表です。</p></Notes>
<footer>記号や色だけに頼らず、各セルに権限を文字で示す。</footer></section>;
}

schema.ts

947 B

/designs/permission-matrix/source/schema.ts

Show contents
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
const value = z.number().finite();
const positive = value.positive();
const unique = (values: string[]) => new Set(values).size === values.length;

export const schema = z.object({
  title: text.describe("結論を伝える見出し"),
  subtitle: text.optional().describe("出典・時点・母集団や仮定"),
  roles:z.array(short).min(2).max(5).describe("役割の名称"),
  actions:z.array(z.object({label:short,permissions:z.array(z.enum(["allow","deny","approval"])).min(2).max(5),condition:text}).strict()).min(3).max(8).describe("操作と役割順の権限、適用条件"),
}).strict().refine(d=>unique(d.roles)&&unique(d.actions.map(a=>a.label))&&d.actions.every(a=>a.permissions.length===d.roles.length),"役割数と権限数を揃え、名称を一意にする");
export type Props = z.infer<typeof schema>;

meta.yaml

1.3 KB

/designs/permission-matrix/source/meta.yaml

Show contents
id: permission-matrix
name:
  ja: 権限と承認のマトリクス
  en: Permission Matrix
purpose:
  ja: 役割と操作の交点に許可・不許可・承認条件を明示する。
  en: Permission Matrix with explicit source values and reading conditions.
status: published
still: ./still.png
category: specification
catalogOrder: 660
jobs:
  - assign-governance
useCases:
  - org
layoutKinds:
  - table
tags:
  - permission-matrix
  - specification
  - offline
  - responsive
whenToUse:
  - ja: 役割と操作の交点に許可・不許可・承認条件を明示する。
    en: Use to communicate permission matrix.
avoidWhen:
  - ja: 同じ条件・単位で比較できないデータには使わない
    en: Do not compare values with incompatible units, populations, or assumptions.
requiredInputs:
  - ja: 記号や色だけに頼らず、各セルに権限を文字で示す。
    en: Provide the source values, labels and decision conditions described by the schema.
relatedLayouts:
  - raci-matrix
  - release-readiness
dataSchema:
  title: string — 結論を伝える見出し
  subtitle: string? — 出典・時点・母集団や仮定
  roles: array (0, 1, 2, 3) — 役割の名称
  actions: array (label, permissions, condition) — 操作と役割順の権限、適用条件
sources: []

sample-data.json

952 B

/designs/permission-matrix/source/sample-data.json

Show contents
{
  "title": "更新権限と承認責任を分ける",
  "subtitle": "架空サンプル|レイアウトの使用例",
  "roles": [
    "受付担当",
    "審査担当",
    "管理者"
  ],
  "actions": [
    {
      "label": "申込閲覧",
      "permissions": [
        "allow",
        "allow",
        "allow"
      ],
      "condition": "担当範囲の申込に限る"
    },
    {
      "label": "申込修正",
      "permissions": [
        "allow",
        "approval",
        "allow"
      ],
      "condition": "承認後は変更記録を残す"
    },
    {
      "label": "審査結果登録",
      "permissions": [
        "deny",
        "allow",
        "approval"
      ],
      "condition": "審査担当者が根拠を記録"
    },
    {
      "label": "権限変更",
      "permissions": [
        "deny",
        "deny",
        "approval"
      ],
      "condition": "別管理者による承認が必要"
    }
  ]
}