New Delhi · September 2026
Preceda

06 Controls

Controls

The acts and fields counsel touches: buttons in five weights, text, search, choices, the combobox, attaching files, correcting a fact in place, and the Paper · Lamp switch.

  1. Button
  2. Text field
  3. Text area
  4. Search field
  5. Select
  6. Checkbox and radio
  7. Switch
  8. Combobox
  9. File attach
  10. Inline edit
  11. Segmented

§ Composed

The sign-in desk.

One field, one binding act, two destinations. Everything on this plate is a package control; nothing is restyled.

Paper

Members sign in

We send a six-digit code; there is no password.

Members sign inRequest an introduction

Lamp

Members sign in

We send a six-digit code; there is no password.

Members sign inRequest an introduction

01

Button

One clear next act. The binding fill is kept for the act the page exists for; the letter act (Send, Open a matter) is set in ink; everything else is ruled or quiet.

Anatomy

.preceda-button
Primary (the 0.1 default): binding fill, action ink, Inter 14, 40px tall.
--secondary
Rule-strong border, ink text, transparent; paper-hover under the hand.
--quiet
Binding-ink text only; binding tint on hover. The least weight an act can have.
--ink
The letter act: ink fill, paper text, set in the serif. Lamp inverts it by itself.
--destructive
Binding rule and binding-ink verb; fills with binding only on hover. The word says what is lost.
--icon
Square, icon only; an aria-label is required (IconButton enforces it).
--sm
32px drawn, 40px hit area (44px on touch) via an invisible extension.
__working
The working point: a small square in the text colour, pulsing unless motion is reduced.
.preceda-button-group
Related acts on one line (role="group"). --attached shares rules; --end aligns right.

Variants

Primary

One per view.

Paper

Lamp

Secondary

Paper

Lamp

Quiet

Paper

Lamp

Ink

The composer’s Send and the register’s Open a matter. Interactive: press Send.

Paper

Lamp

Destructive

Name the loss. Confirmation belongs to ConfirmDialog (T4).

Paper

Lamp

Icon only

Paper

Lamp

Sizes

md (40px) is the default; sm for dense rows.

Paper

Lamp

Loading

Present-tense label and the working point. Focus stays on the button.

Paper

Lamp

Group

Paper

Lamp

State matrix

StatePaperLamp
default
hover
focus-visible
active
disabled
loading

At 390 px

On a coarse pointer every button grows to 44px. In a sheet or on the register, the one act runs full width at the foot; a second act sits above it as secondary.

390 px

Accessibility

  • Native <button type="button"> by default; asChild renders a router link with the same face when the act navigates.
  • Loading sets aria-busy and aria-disabled (not disabled), so focus and the accessible name stay while the label reads “Sending…”.
  • IconButton requires label; the glyph is aria-hidden. Never rely on title= for a name.
  • Disabled is flat paper-sunken with tertiary ink and no hover; explain why nearby, or prefer an error on submit.
  • Targets: 40px (32px drawn + extension for sm); 44px under pointer: coarse.

Do

  • Write the act as a verb and its object: “Approve for filing”, “Delete this draft”.
  • Use ink for the letter act — the one that sends words to the associate or opens a new matter.
  • Keep one primary per view; pair it with a secondary when there is a way back.

Don’t

  • Put two binding fills side by side, or use destructive for anything reversible.
  • Use an icon-only button where a word fits; “More” is the exception.
  • Animate the label or swap it for a spinner; the working point is the only motion.

Usage

HTML
<button class="preceda-button" type="button">Prepare the matter</button>
<button class="preceda-button preceda-button--secondary" type="button">Open the FIR</button>
<button class="preceda-button preceda-button--ink" type="button">Send</button>
<button class="preceda-button preceda-button--ink" type="button" aria-busy="true" aria-disabled="true">
  <span class="preceda-button__working" aria-hidden="true"></span>Sending…
</button>
<button class="preceda-button preceda-button--quiet preceda-button--icon" type="button" aria-label="More actions">
  <svg class="preceda-icon preceda-icon--20" aria-hidden="true"><use href="/icons.svg#preceda-icon-more"/></svg>
</button>
<div class="preceda-button-group" role="group" aria-label="Filing">…</div>
React
import { Button, ButtonGroup, IconButton } from '@luvkapur/preceda-design-system/react';

<Button variant="ink" loading={sending} loadingLabel="Sending…" data-matter-send onClick={send}>Send</Button>
<IconButton icon="more" label="More actions on this matter" />
<Button asChild variant="secondary"><RouterLink to="/matters">Back to matters</RouterLink></Button>
<ButtonGroup aria-label="Filing">
  <Button variant="secondary">Return to associate</Button>
  <Button>Approve for filing</Button>
</ButtonGroup>

02

Text field

A labelled line for short data: an address, a code, a case number. The label sits above in small caps, the hint and the error below, and the error is announced with the field.

Anatomy

.preceda-field-group
The stack: label, control, then the foot (error before hint).
.preceda-field-label
Small-caps label role in secondary ink; __optional adds “· optional” in tertiary.
.preceda-field
The 0.1 control: rule-strong border on paper-raised, Inter 14, 40px. Hover darkens the rule; focus draws the opaque ring.
--code
One-time codes and identifiers: mono, tabular, tracked, centred.
--ruled
Only a bottom rule, in the serif: an answer written inside a letter or fact request.
.preceda-field-hint / .preceda-field-error
Small role; the error is binding ink with a leading dash and is referenced by aria-describedby.

Variants

Default

Paper

We send a six-digit code; there is no password.

Lamp

We send a six-digit code; there is no password.

Code

The sign-in code: numeric keypad, one-time-code autocomplete.

Paper

Lamp

Ruled

An answer set within correspondence (the associate’s numbered questions).

Paper

Lamp

Optional

Paper

Lamp

State matrix

StatePaperLamp
empty
filled
hover
focus-visible
error

Enter the whole address, e.g. rhea@mehrachambers.in.

Enter the whole address, e.g. rhea@mehrachambers.in.

disabled

Sending the code…

Sending the code…

read-only

At 390 px

Full width, 44px tall on touch, and set at 17px under a coarse pointer so iOS does not zoom into the field. Use the right type, inputMode and autoComplete so the phone offers the right keyboard and the code from Messages.

390 px

Accessibility

  • A visible <label for> is always present; placeholder text is an example, never the label.
  • The error sets aria-invalid and is listed first in aria-describedby, then the hint.
  • Placeholder ink is tertiary (passes 4.5:1); disabled values stay in tertiary ink, legible but inert.
  • Focus ring is the shared opaque binding outline, offset 2px.

Do

  • Say what the field wants in the label and give an example in the placeholder.
  • Write errors as the fix: “Enter the whole address…”.

Don’t

  • Use placeholder text as the only label.
  • Validate on every keystroke; validate on blur or submit.

Usage

HTML
<div class="preceda-field-group">
  <label class="preceda-field-label" for="email">Email address</label>
  <input class="preceda-field" id="email" type="email" aria-invalid="true" aria-describedby="email-error">
  <div class="preceda-field-group__foot">
    <p class="preceda-field-error" id="email-error">Enter the whole address, e.g. rhea@mehrachambers.in.</p>
  </div>
</div>
React
import { TextField } from '@luvkapur/preceda-design-system/react';

<TextField label="Email address" type="email" autoComplete="email" value={email} onChange={…} error={error} />
<TextField label="Six-digit code" variant="code" inputMode="numeric" autoComplete="one-time-code" maxLength={6} />

03

Text area

Counsel’s own writing, set in the reading serif. It grows with what is written up to a cap, then scrolls; it never shows a resize grip in the composer.

Anatomy

.preceda-field--textarea
Serif body (15/1.55); rows sets the resting height, no resize grip.
TextArea autoGrow
React fallback: measures scrollHeight on every change, capped at maxRows lines, then overflow scrolls.
onSubmitShortcut
The composer pattern: Enter submits a non-blank value, Shift+Enter inserts a newline, IME composition is respected.
.preceda-field-counter
Optional “n / max” under the field when showCount and maxLength are set.
--resizable
autoGrow={false}: fixed height with a vertical grip, for long notes outside the composer.

Variants

Instruction (interactive)

Type, press Enter to send, Shift+Enter for a new line.

Paper

Enter sends · Shift+Enter starts a new line

Lamp

Enter sends · Shift+Enter starts a new line

With count

Paper

85 / 500

Lamp

85 / 500

Grown to its cap

Paper

Lamp

State matrix

StatePaperLamp
empty
hover
focus-visible
error

State at least one ground before the associate drafts.

State at least one ground before the associate drafts.

disabled

The associate is working; you can write again when it replies.

The associate is working; you can write again when it replies.

At 390 px

In the dock the composer is one line that grows to four before it scrolls, so the desk stays in view. Under a coarse pointer the serif rises to the 17px reading size so iOS does not zoom into the field.

390 px

Accessibility

  • Label it: a visible label, or aria-label (“Instruct the associate”) where the placeholder carries the prompt.
  • Explain the Enter/Shift+Enter shortcut in the hint; it is announced with the field.
  • The counter is referenced by aria-describedby but not live, so it is not read on every keystroke.

Do

  • Keep the composer placeholder an invitation to ask or instruct: “Ask a question or instruct the associate…”.
  • Cap growth so the send act never leaves the viewport.

Don’t

  • Show a resize grip in the composer.
  • Clear what counsel wrote when a send fails.

Usage

HTML
<textarea class="preceda-field preceda-field--textarea" rows="2"
  aria-label="Instruct the associate" placeholder="Ask a question or instruct the associate…"></textarea>
React
import { TextArea } from '@luvkapur/preceda-design-system/react';

<TextArea
  ref={composerRef}
  aria-label="Instruct the associate"
  placeholder="Ask a question or instruct the associate…"
  rows={1}
  maxRows={8}
  value={value}
  onChange={(e) => setValue(e.target.value)}
  onSubmitShortcut={() => canSend && send()}
  disabled={sending}
/>

04

Search field

Search the record with one line. A glyph says what it is, a shortcut says how to reach it, and one status line says what came back.

Anatomy

.preceda-search
The line: search glyph at the start, the field, and the end slot.
.preceda-search__shortcut
A <kbd> hint (“/”) shown while the field is empty; hidden from assistive technology (the shortcut is documented in the shortcuts sheet).
Clear act
IconButton “Clear search” replaces the shortcut once there is text; Escape also clears.
.preceda-search__status
role="status": “Searching…” with the working point, the count, or the failure. Reserve it with status="" to keep the layout still.
--lg
The front-desk console: 48px, the reading serif at 17px.

Variants

Console (interactive)

Type three letters; Escape or the cross clears.

Paper

Lamp

In a pane

Paper

3 places · paragraphs 7, 11, 12

Lamp

3 places · paragraphs 7, 11, 12

State matrix

StatePaperLamp
empty
hover
focus-visible
loading

Searching…

Searching…

results

412 judgments · sorted by relevance

412 judgments · sorted by relevance

empty result

Nothing in the corpus matches. Check the citation or search by party name.

Nothing in the corpus matches. Check the citation or search by party name.

error

Search is unavailable for a moment. Try again.

Search is unavailable for a moment. Try again.

disabled

At 390 px

Full width; the shortcut hint is dropped on touch (there is no “/” key to reach). The status line holds its height so results do not jump as it changes.

390 px

Accessibility

  • type="search" gives the searchbox role and the platform clear-on-Escape; the native cancel glyph is hidden in favour of the labelled cross.
  • Status changes are polite live announcements (“412 judgments”), never alerts.
  • The shortcut is a hint only; the page must register the key and must not steal it while another field has focus.

Do

  • Say what can be searched in the placeholder.
  • Report zero results with a next step.

Don’t

  • Put a Search button beside a field that searches as you type.
  • Replace the status line with a spinner.

Usage

HTML
<div class="preceda-search">
  <svg class="preceda-icon preceda-search__glyph" aria-hidden="true"><use href="/icons.svg#preceda-icon-search"/></svg>
  <input class="preceda-field" type="search" aria-label="Search" aria-describedby="s-status">
  <span class="preceda-search__end"><kbd class="preceda-search__shortcut" aria-hidden="true">/</kbd></span>
</div>
<p class="preceda-search__status" id="s-status" role="status">412 judgments</p>
React
import { SearchField } from '@luvkapur/preceda-design-system/react';

<SearchField
  ref={searchRef}
  aria-label="Search cases, citations or legal topics"
  shortcut="/"
  size="lg"
  value={query}
  onChange={(e) => updateQuery(e.target.value)}
  onClear={() => updateQuery('')}
  loading={searching}
  status={searching ? 'Searching…' : resultLine}
/>

05

Select

The platform’s own list, given a ruled face. The native control keeps the keyboard, the phone’s wheel and the screen reader; the package draws the field and one chevron.

Anatomy

.preceda-select
Wrapper that positions the chevron; takes the quiet modifier.
.preceda-select__control
The <select> with appearance: none: the field face, 40px, Inter 14, room for the chevron.
.preceda-select__chevron
chevron-down icon in secondary ink; pointer-events: none.
--quiet
A bare word and chevron for running chrome: “Sort: Relevance”, “Desk ▾”.

Variants

Default

Paper

Where the application will be filed.

Lamp

Where the application will be filed.

Quiet

For a sort or a view switch inside chrome.

Paper

Lamp

Grouped options

Paper

Lamp

State matrix

StatePaperLamp
default
hover
focus-visible
error

Choose the court the application goes to.

Choose the court the application goes to.

disabled

At 390 px

The phone opens its own picker; nothing to build. The control grows to 44px and 17px text so iOS does not zoom.

390 px

Accessibility

  • Always native: no custom listbox for a closed set of choices. Use Combobox only when the set is searched.
  • Label with a visible <label> or aria-label; the error sets aria-invalid and aria-describedby.
  • Disabled options stay listed so counsel can see what exists but does not apply.

Do

  • Use for five to fifteen known choices.
  • Put a “Choose a …” first option when there is no sensible default.

Don’t

  • Use for two options; use Segmented or radios.
  • Restyle the open list; it belongs to the platform.

Usage

HTML
<div class="preceda-select">
  <select class="preceda-select__control" id="forum">…</select>
  <svg class="preceda-icon preceda-icon--16 preceda-select__chevron" aria-hidden="true"><use href="/icons.svg#preceda-icon-chevron-down"/></svg>
</div>
React
import { Select } from '@luvkapur/preceda-design-system/react';

<Select label="Forum" options={courts} value={forum} onChange={(e) => setForum(e.target.value)} />
<Select aria-label="Sort judgments" variant="quiet" options={sort} />

06

Checkbox and radio

The native choice, redrawn as a printed box or ring. The label is the target; checked is a mark (a tick, a dot), never a colour alone.

Anatomy

.preceda-choice
The <label>: a two-column grid (mark, words), at least 40px tall, 44px on touch.
.preceda-choice__input
The native input, appearance: none: a 16px box (radius small) or ring in secondary ink on paper-raised.
:checked
Binding fill with an action-ink tick (checkbox) or dot (radio).
:indeterminate
A dash for “some of these”; React sets the property and aria-checked="mixed".
.preceda-choice__hint
One line under the label in the small role, tied by aria-describedby.
.preceda-choice-group
A fieldset with a small-caps legend; --inline sets the choices on one line.

Variants

Checkboxes

Paper

Annexures on file

Lamp

Annexures on file

Mixed

Paper

Before this is filed

Lamp

Before this is filed

Radios

Paper

Forum

Lamp

Forum

State matrix

StatePaperLamp
default
hover
focus-visible
error
Declaration

Confirm the declaration before filing.

Declaration

Confirm the declaration before filing.

disabled

At 390 px

On touch the box grows to 20px and the row to 44px; the whole label stays the target. Stack choices; do not set them inline below 390px.

390 px

Annexures on file

Accessibility

  • Native inputs: Space toggles, arrows move within a radio group, the form submits them.
  • The label wraps the input, so the words are the name and the hit area.
  • Checked is shown by a tick or dot on a filled box, so it reads without colour; indeterminate is a dash and aria-checked="mixed".
  • A group’s error is on the fieldset (aria-describedby + aria-invalid) and each invalid input carries a binding border.

Do

  • Use a checkbox for independent facts, a radio for one of a few.
  • Write labels as statements that are true when checked.

Don’t

  • Use a checkbox for a setting that applies at once; use Switch.
  • Pre-check a declaration.

Usage

HTML
<label class="preceda-choice">
  <input class="preceda-choice__input" type="checkbox" name="notice">
  <span class="preceda-choice__label">Notice under s. 35(3) BNSS served</span>
</label>
React
import { Checkbox, ChoiceGroup, Radio } from '@luvkapur/preceda-design-system/react';

<ChoiceGroup legend="Forum" inline>
  <Radio name="forum" value="sessions" label="Sessions Court" />
  <Radio name="forum" value="hc" label="High Court" />
</ChoiceGroup>
<Checkbox label="All authorities verified" indeterminate={some && !all} checked={all} />

07

Switch

A setting that takes effect the moment it changes. A ruled track, a square thumb, and the state said in a word, so on and off never rest on colour.

Anatomy

.preceda-switch
A <button role="switch" aria-checked>; the label (and hint) are its name.
__track / __thumb
32 × 16 ruled track with a square thumb. On: binding fill, action-ink thumb moved to the end.
__state
“On” / “Off” in the small role after the track (aria-hidden; the role carries the state).
--row
A settings line: the label takes the measure, the track sits at the end over a hairline.

Variants

Settings rows (interactive)

Click or press Space.

Paper

Lamp

Inline

Paper

Lamp

State matrix

StatePaperLamp
off
on
hover
focus-visible
disabled

At 390 px

Rows run full width with the track at the end, 44px tall on touch. Keep one setting per row.

390 px

Accessibility

  • WAI-ARIA switch: role="switch" with aria-checked; Space and Enter toggle (it is a button).
  • The visible label is the accessible name; the “On/Off” word is decorative duplication for sighted readers.
  • The track moves the thumb and changes its fill, so the state reads without colour.

Do

  • Use for a preference that applies immediately and can be undone by switching back.
  • Name the setting, not the action: “Lamp after 7 pm”.

Don’t

  • Use inside a form that is submitted later; use a checkbox.
  • Pair a switch with a Save button.

Usage

HTML
<button class="preceda-switch preceda-switch--row" type="button" role="switch" aria-checked="false">
  <span class="preceda-switch__text"><span class="preceda-switch__label">Lamp after 7 pm</span></span>
  <span class="preceda-switch__track" aria-hidden="true"><span class="preceda-switch__thumb"></span></span>
  <span class="preceda-switch__state" aria-hidden="true">Off</span>
</button>
React
import { Switch } from '@luvkapur/preceda-design-system/react';

<Switch label="Lamp after 7 pm" checked={auto} onCheckedChange={setAuto} className="preceda-switch--row" />

08

Combobox

A field that suggests from the record: judges, counsel, police stations, acts. Focus never leaves the field; the list hangs beneath it on a raised sheet and the active suggestion carries a binding rule.

Anatomy

input[role="combobox"]
The field (the .preceda-field face) with aria-autocomplete="list", aria-expanded, aria-controls and aria-activedescendant.
.preceda-combobox__indicator
Chevron (turned while open), or the working point while suggestions load.
.preceda-combobox__listbox
role="listbox" on paper-raised with the sheet shadow, 240px before it scrolls; layer: popover.
.preceda-combobox__option
role="option": the label in Inter, one meta line in tertiary; aria-selected (active) draws the wash and a binding start rule; the value already set carries a tick.
.preceda-combobox__message
“Searching…” or “No matches found”, inside the list.
.preceda-combobox__live
A polite status: “4 suggestions. Use the arrow keys to choose.”

Variants

Judge (interactive)

Try “Del”, “Iyer” or “Saket”.

Paper

Two letters to start. Arrow keys to choose; Enter to set.

Lamp

Two letters to start. Arrow keys to choose; Enter to set.

State matrix

StatePaperLamp
closed
open
  • Meera S. IyerDelhi High Court · 2,311 judgments
  • Arvind K. MenonDelhi High Court · 1,477 judgments
  • Sunita RaoDelhi High Court · 902 judgments
3 suggestions. Use the arrow keys to choose.
  • Meera S. IyerDelhi High Court · 2,311 judgments
  • Arvind K. MenonDelhi High Court · 1,477 judgments
  • Sunita RaoDelhi High Court · 902 judgments
3 suggestions. Use the arrow keys to choose.
active option
  • Meera S. IyerDelhi High Court · 2,311 judgments
  • Arvind K. MenonDelhi High Court · 1,477 judgments
  • Sunita RaoDelhi High Court · 902 judgments
3 suggestions. Use the arrow keys to choose.
  • Meera S. IyerDelhi High Court · 2,311 judgments
  • Arvind K. MenonDelhi High Court · 1,477 judgments
  • Sunita RaoDelhi High Court · 902 judgments
3 suggestions. Use the arrow keys to choose.
loading
Searching…
Searching…
empty
No matches found
No matches found
error

Suggestions are unavailable; type the full name.

Suggestions are unavailable; type the full name.

disabled

At 390 px

The list stays anchored under the field and scrolls inside itself (overscroll contained) so the page does not move under the thumb. Each suggestion is 44px on touch. Inside a bottom sheet, keep the field above the keyboard.

390 px

  • Chandramauli Kr. PrasadSupreme Court of India · 1,204 judgments
  • Pinaki Chandra GhoseSupreme Court of India · 986 judgments
  • Meera S. IyerDelhi High Court · 2,311 judgments
3 suggestions. Use the arrow keys to choose.

Accessibility

  • WAI-ARIA 1.2 combobox, list autocomplete without automatic selection: DOM focus stays in the input; aria-activedescendant names the active option.
  • ArrowDown opens and moves to the first suggestion (Alt+ArrowDown opens only); ArrowUp opens at the last; Enter sets the active suggestion; Escape closes, and a second Escape clears; Tab closes and leaves.
  • Pointer: mousedown on an option is prevented so the field keeps focus; hovering moves the active option.
  • Counts and “No matches found” are announced politely; the tick on the current value has the text “current”.

Do

  • Let the product fetch and debounce; pass what came back as options with a meta line that tells two similar names apart.
  • Keep what counsel typed if they leave without choosing.

Don’t

  • Use for a short closed list; use Select.
  • Auto-select the first suggestion on Enter when none is active.

Usage

HTML
<div class="preceda-field-group">
  <label class="preceda-field-label" for="judge">Judge</label>
  <div class="preceda-combobox" data-open="true">
    <div class="preceda-combobox__anchor">
      <input class="preceda-field" id="judge" role="combobox" aria-autocomplete="list"
        aria-expanded="true" aria-controls="judge-list" aria-activedescendant="judge-o1">
      <span class="preceda-combobox__indicator" aria-hidden="true">…</span>
    </div>
    <ul class="preceda-combobox__listbox" id="judge-list" role="listbox">
      <li class="preceda-combobox__option" id="judge-o1" role="option" aria-selected="true">
        <span class="preceda-combobox__option-label">Meera S. Iyer</span>
        <span class="preceda-combobox__option-meta">Delhi High Court · 2,311 judgments</span>
      </li>
    </ul>
  </div>
</div>
React
import { Combobox } from '@luvkapur/preceda-design-system/react';

<Combobox
  label="Judge"
  options={suggestions}          // [{ value, label, meta }]
  inputValue={draft}
  onInputValueChange={(v) => { setDraft(v); fetchSuggestions(v); }}
  onSelect={(o) => save(o.label)}
  loading={loading}
  minChars={2}
/>

09

File attach

Attaching is a word, not a paperclip in a bubble. The act says what to attach; attached files are named on one line with their state in words; dropping a file anywhere on the composer works too.

Anatomy

.preceda-file-attach
The act (“Attach”, an underlined text button) and its hint (“FIR, orders, correspondence · PDF, DOCX”) in the small role; a hidden file input.
.preceda-attached
The line: “Attached:” then each file between middots.
__name
The file name in ink (a button when the finished file can be opened on the desk); truncates past 192px with the full name as a tooltip.
__detail / __working / __error
Size or length; the working point with “uploading”; the reason in binding ink, announced as an alert.
__remove
A small labelled cross (“Remove FIR.pdf”); disabled while a removal or turn is in flight.
.preceda-dropzone
Wraps the composer. While a file is held over it: a dashed binding rule and “Drop to attach to this matter”.
--panel
A standing drop place for matter creation: dashed rule-strong, a serif line and the Attach act.

Variants

Composer (interactive)

Choose files or drop them on the box; remove with the cross.

Paper

Ask a question or instruct the associate…
  • 412 KB
FIR, orders, correspondence · PDF, DOCX

Lamp

Ask a question or instruct the associate…
  • 412 KB
FIR, orders, correspondence · PDF, DOCX

Attached line

Paper

  • 412 KB
  • Order 12 Aug.pdfuploading
  • Passport scan.heicnot a PDF, DOCX or TXT

Lamp

  • 412 KB
  • Order 12 Aug.pdfuploading
  • Passport scan.heicnot a PDF, DOCX or TXT

Panel

Before a matter exists (the front desk, /matters).

Paper

Drop the FIR, orders or correspondence here

PDF, DOCX, TXT · 30 MB each

Lamp

Drop the FIR, orders or correspondence here

PDF, DOCX, TXT · 30 MB each

State matrix

StatePaperLamp
act · default
FIR, orders, correspondence · PDF, DOCX
FIR, orders, correspondence · PDF, DOCX
act · hover
FIR, orders, correspondence · PDF, DOCX
FIR, orders, correspondence · PDF, DOCX
act · focus-visible
FIR, orders, correspondence · PDF, DOCX
FIR, orders, correspondence · PDF, DOCX
act · disabled
Attach again when the associate replies
Attach again when the associate replies
uploading
  • Order 12 Aug.pdfuploading
  • Order 12 Aug.pdfuploading
attached
  • 412 KB
  • 88 KB
  • 412 KB
  • 88 KB
error
  • Bundle.ziplarger than 30 MB
  • Bundle.ziplarger than 30 MB
remove disabled
  • FIR.pdf412 KB
  • FIR.pdf412 KB
dragging
Ask a question or instruct the associate…
Ask a question or instruct the associate…
panel · dragging

Drop the FIR, orders or correspondence here

Drop the FIR, orders or correspondence here

At 390 px

There is no drag on a phone: the Attach act opens the system picker (Files, Photos, Camera). The attached line wraps under the composer; names truncate before the cross so the remove target never moves off the line.

390 px

  • FIR 214-2026 PS Saket.pdf412 KB
  • Order 12 Aug.pdfuploading
  • Passport scan.heicnot a PDF, DOCX or TXT
PDF, DOCX

Accessibility

  • The act is a real <button>; the hidden input has its own name (“Attach files”). Dropping is never the only way in.
  • Upload state is in words (“uploading”, the reason for a failure); failures are role="alert", progress is role="status".
  • Each remove act is named for its file. After a removal the product returns focus to the Attach act.
  • The drop overlay is aria-hidden: it repeats what the act already says.

Do

  • Say what belongs here in the hint (FIR, orders, correspondence) and the limits.
  • Name the reason a file failed in counsel’s words.

Don’t

  • Render files as chips or pills.
  • Start a turn per file; files queue for the next Send.

Usage

HTML
<ul class="preceda-attached" aria-label="Attached files">
  <li class="preceda-attached__label" aria-hidden="true">Attached</li>
  <li class="preceda-attached__item" data-status="done">
    <button class="preceda-attached__name" type="button">FIR.pdf</button>
    <span class="preceda-attached__detail">412 KB</span>
    <button class="preceda-button preceda-button--quiet preceda-button--sm preceda-button--icon preceda-attached__remove" aria-label="Remove FIR.pdf">…</button>
  </li>
</ul>
<span class="preceda-file-attach">
  <input type="file" hidden multiple accept=".pdf,.docx,.txt" aria-label="Attach files">
  <button class="preceda-file-attach__trigger" type="button">Attach</button>
  <span class="preceda-file-attach__hint">FIR, orders, correspondence · PDF, DOCX</span>
</span>
React
import { AttachedList, Dropzone, FileAttach } from '@luvkapur/preceda-design-system/react';

<Dropzone onFiles={handleFiles} disabled={sending}>
  <TextArea … />
  <AttachedList
    files={pending.map((f) => ({ id: f.localId, name: f.filename, status: f.status, detail: formatSize(f.sizeBytes), error: f.error }))}
    onOpen={(f) => view(f.id)}
    onRemove={(f) => remove(f.id)}
    removeDisabled={removing}
  />
  <FileAttach onFiles={handleFiles} accept={MATTER_UPLOAD_ACCEPT} hint="FIR, orders, correspondence · PDF, DOCX" disabled={sending} />
</Dropzone>

10

Inline edit

A recorded fact counsel can correct where it stands. At rest it reads as the record; under the hand a rule and the pen say it can change. Enter keeps the change, Escape restores it.

Anatomy

.preceda-inline-edit
Label | value grid (label column 120px), like a ledger row.
__label
Small-caps label role in secondary ink.
__value
A <button> showing the value in the serif (or the placeholder in tertiary italic); hover and focus draw a bottom rule and the edit glyph.
__input
The editing field: a bottom rule in ink, same serif and size, so nothing jumps.
__meta
One line of context from the record under the value (the judge’s bench, a counsel’s practice).
__status
“Saving…” with the working point, or the reason it failed in binding ink.
--title / --stacked
A heading renamed in place (matter title, draft name); label above value for narrow columns.

Variants

Ledger rows (interactive)

Click a value, type, press Enter or Escape. The police station must start “PS”.

Paper

Judge

Court of Sessions, Saket · 318 orders · bail granted in 61%

Opposing counsel
Police station

Lamp

Judge

Court of Sessions, Saket · 318 orders · bail granted in 61%

Opposing counsel
Police station

Title

Paper

Lamp

Stacked

Paper

Forum

Lamp

Forum

State matrix

StatePaperLamp
empty
Opposing counsel
Opposing counsel
filled
Judge
Judge
hover
Judge
Judge
focus-visible
Judge
Judge
editing
Police station
Police station
saving
Police station

Saving…

Police station

Saving…

error
Police station

Name the station as “PS …”, as the FIR does.

Police station

Name the station as “PS …”, as the FIR does.

with meta
Judge

Court of Sessions, Saket · 318 orders · bail granted in 61%

Judge

Court of Sessions, Saket · 318 orders · bail granted in 61%

meta loading
Judge

Reading the record…

Judge

Reading the record…

At 390 px

Below 390px use stacked (label above value). On touch the pen is always shown, because there is no hover to reveal it, and the field is set at 17px so iOS does not zoom.

390 px

Judge
Opposing counsel

Accessibility

  • At rest the value is a button named “Judge Additional Sessions Judge-03, South, edit”; activating it moves focus into the field with the text selected.
  • Enter saves, Escape restores the recorded value; leaving the field saves. Focus returns to the value afterwards.
  • Saving sets aria-busy on the field; a failure keeps the editor open, sets aria-invalid and is announced as status.
  • For suggestions, pass renderEditor and spread its inputProps onto a Combobox so the keys and name survive.

Do

  • Use for single recorded facts: judge, forum, opposing counsel, the matter title.
  • Show where a suggestion came from in the meta line.

Don’t

  • Use for anything that needs review before it counts (use a form with a Save act).
  • Save an unchanged or blank value.

Usage

HTML
<div class="preceda-inline-edit">
  <span class="preceda-inline-edit__label" id="j-label">Judge</span>
  <div class="preceda-inline-edit__body">
    <button class="preceda-inline-edit__value" type="button" aria-labelledby="j-label j-value" id="j-value">
      <span class="preceda-inline-edit__text">Additional Sessions Judge-03, South</span>
      <svg class="preceda-icon preceda-icon--16 preceda-inline-edit__glyph" aria-hidden="true">…</svg>
    </button>
    <p class="preceda-inline-edit__meta">Court of Sessions, Saket · 318 orders</p>
  </div>
</div>
React
import { Combobox, InlineEdit } from '@luvkapur/preceda-design-system/react';

<InlineEdit label="Judge" value={judge} onSave={saveJudge} meta={stats?.line1} metaLoading={loadingStats}
  renderEditor={({ inputProps, onChange, commit }) => (
    <Combobox {...inputProps} options={suggestions} inputValue={String(inputProps.value)}
      onInputValueChange={onChange} onSelect={(o) => { onChange(o.label); commit(); }} />
  )}
/>

11

Segmented

One of a few settings, applied at once. By default it is typographic — the words between middots, the chosen one in ink over a binding rule — so the theme switch reads like the account page: “Paper · Lamp”.

Anatomy

.preceda-segmented
role="radiogroup" with an accessible name; __label shows it before the options.
__option
role="radio" buttons; one tab stop (the checked one). Middots between options are drawn, not text.
[data-line-start]
Set by the component on the first option of each wrapped line; its middot is dropped, so no line opens with one.
__text
The word; aria-checked="true" puts it in ink over a 1px binding rule.
--ui
The same in small Inter, for the masthead account line and toolbars.
--ruled
Boxed segments for dense tool rows; the chosen segment is set in ink with paper text.

Variants

Paper · Lamp (interactive)

Arrow keys move and apply; the sample below changes its lighting.

Paper

No notice under Section 35(3) BNSS was served on the Applicant.

Lamp

No notice under Section 35(3) BNSS was served on the Applicant.

Chrome size

Paper

Lamp

Ruled

A sort switch above a register.

Paper

Lamp

State matrix

StatePaperLamp
default
hover
focus-visible
wrapped
disabled option

At 390 px

Options grow to 44px targets with a wider gap. Keep to two or three short words; past that, use a Select.

390 px

Accessibility

  • WAI-ARIA radio group: Tab reaches the checked option; arrows move and select; Home and End jump.
  • The chosen option is said by aria-checked and drawn with a rule under it as well as ink, so it does not rest on colour.
  • Name the group (“Lighting”); show the name with showLabel when context does not make it obvious.

Do

  • Apply the choice immediately (theme, sort, view).
  • Use the typographic form in chrome and settings; the ruled form in tool rows.

Don’t

  • Use for navigation between pages; use Tabs or links.
  • Use icons without words (sun/moon) for Paper and Lamp.

Usage

HTML
<div class="preceda-segmented" role="radiogroup" aria-label="Lighting">
  <button class="preceda-segmented__option" type="button" role="radio" aria-checked="true"><span class="preceda-segmented__text">Paper</span></button>
  <button class="preceda-segmented__option" type="button" role="radio" aria-checked="false" tabindex="-1"><span class="preceda-segmented__text">Lamp</span></button>
</div>
React
import { Segmented } from '@luvkapur/preceda-design-system/react';

const { theme, setTheme } = useTheme();
<Segmented label="Lighting" options={[{ value: 'paper', label: 'Paper' }, { value: 'lamp', label: 'Lamp' }]}
  value={theme} onValueChange={setTheme} variant="ui" />