# ContactInquiryPanel

> Contact page inquiry panel with message and book-a-call tabs wrapping the editorial form.

- Status: stable
- Group: form
- Page: https://www.digitaltableteur.com/design-system/components/contact-inquiry-panel
- Contract: https://www.digitaltableteur.com/contracts/v1/ContactInquiryPanel.contract.json

## When to use

ContactInquiryPanel is the contact page's inquiry column: a two-tab switcher between writing a message and booking a call. The parent owns the form and its submit state, passing it in as messagePanel (ContactPage passes ContactFormEditorial); packageId preselects a pricing package for booking CTAs deep-linked from /pricing. The message panel anchors at #contact-form for deep links. A ref exposes scrollToMessageForm(), which switches to the message tab, smooth-scrolls to #contact-form and focuses its first enabled field.

## When not to use

- Do not mount without messagePanel slot.
- Do not duplicate tablist landmarks on the same page.
- Don't use it for a standalone form without tab chrome; mount ContactFormEditorial directly.

## Look-alikes

The contract names no look-alike with a deciding condition.

## Props

| Prop | Type | Default | Description | Required |
| --- | --- | --- | --- | --- |
| `initialMode` | `"message" \| "book"` | `message` | Tab to show; defaults to message. The panel switches to it again whenever the prop value changes after mount. | No |
| `packageId` | `string` | none | Optional pricing package id for booking prefill. | No |
| `bookingNotes` | `string` | none | Optional note carried into the booking form, such as the calculator choice. | No |
| `messagePanel` | `ReactNode` | none | Editorial contact form slot shown on the message tab. | Yes |
| `bookingConfig` | `SiteBookingConfig` | none | Booking config used only when packageId is not set; packageId wins and this prop is then ignored. Without either, the site default booking config applies. | No |

## Variants

- Allowed `initialMode` values: `message`, `book` (default `message`).

## Invalid combinations

The contract records no invalid combinations.

## Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Moves focus to the control and on to the next focusable element. (standard) |
| ArrowLeft | Moves focus or the value in that direction. (standard) |
| ArrowRight | Moves focus or the value in that direction. (standard) |
| Enter | Activates the focused control. (standard) |

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

## Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

| Level | Rule | How it is checked |
| --- | --- | --- |
| MUST | ARIA: Tablist with aria-selected tabs. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Tabpanels linked via aria-controls. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Message form anchor id contact-form. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | Required stories produce no axe violations at the configured severity. | Automated by `npm run test:stories:matrix:ci`. WCAG 2.5.8. |
| MUST | The accessibility tree (role, name, state) matches the committed snapshot for every required story. | Automated by `npm run test:stories:matrix:ci`. WCAG 1.3.1, 4.1.2. |
| MUST | The component remains legible and operable under a real browser forced-colors: active pass. | Automated by `npm run test:stories:hc`. |
| MUST | Every documented keyboard interaction works as specified. | Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2. |
| MUST | A human completed an end-to-end accessibility review of this component. | Manual review, last on 2026-11-04. |
| SHOULD | Let the parent own form submit state and pass the editorial form in via messagePanel; the panel only owns the tab chrome. | Not stated in the contract. |
| SHOULD | Deep-link the message tab through the #contact-form anchor; booking CTAs elsewhere can preselect a package with packageId. | Not stated in the contract. |
| SHOULD NOT | Don't rebuild pricing package cards inside the panel; that's PricingPageContent's job. | Not stated in the contract. |

## Install and import

- In-repo only. ContactInquiryPanel is not exported from `@digitaltableteur/react`, so there is no public import path.

## Links

- Machine-readable contract (Design System Contract 1.0): https://www.digitaltableteur.com/contracts/v1/ContactInquiryPanel.contract.json
- Markdown version of this page: https://www.digitaltableteur.com/design-system/components/contact-inquiry-panel.md
- Component index as JSON: https://www.digitaltableteur.com/design-system/components.json
- All components: https://www.digitaltableteur.com/design-system/components
- Source: The implementation lives in a private repository. This page, its Markdown twin and the contract are generated from it at build time and are the public source of truth.
