or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-components.mdindex.mdinteractive-items.mdmenu-items.mdsubmenus.mdutilities.md

submenus.mddocs/

0

# Submenus

1

2

Components for creating nested submenus and additional visual elements like arrows, enabling multi-level menu hierarchies.

3

4

## Capabilities

5

6

### MenubarSub (Sub)

7

8

Container for submenu functionality with controllable open state, managing the visibility and behavior of nested menu content.

9

10

```typescript { .api }

11

/**

12

* Container for submenu with controllable open state

13

* @param props - Submenu container props

14

* @returns JSX element representing the submenu container

15

*/

16

function MenubarSub(props: MenubarSubProps): React.ReactElement;

17

18

interface MenubarSubProps {

19

/** Child components (typically SubTrigger and SubContent) */

20

children?: React.ReactNode;

21

/** Controlled open state */

22

open?: boolean;

23

/** Default open state for uncontrolled usage (default: false) */

24

defaultOpen?: boolean;

25

/** Callback fired when open state changes */

26

onOpenChange?: (open: boolean) => void;

27

}

28

```

29

30

**Usage Examples:**

31

32

```typescript

33

'use client';

34

import * as Menubar from "@radix-ui/react-menubar";

35

36

// Basic submenu

37

<Menubar.Sub>

38

<Menubar.SubTrigger>

39

Export

40

<ChevronRightIcon />

41

</Menubar.SubTrigger>

42

<Menubar.Portal>

43

<Menubar.SubContent>

44

<Menubar.Item>Export as PDF</Menubar.Item>

45

<Menubar.Item>Export as Image</Menubar.Item>

46

</Menubar.SubContent>

47

</Menubar.Portal>

48

</Menubar.Sub>

49

50

// Controlled submenu

51

function ControlledSubmenu() {

52

const [isOpen, setIsOpen] = React.useState(false);

53

54

return (

55

<Menubar.Sub open={isOpen} onOpenChange={setIsOpen}>

56

<Menubar.SubTrigger>

57

Tools {isOpen ? "▼" : "▶"}

58

</Menubar.SubTrigger>

59

<Menubar.Portal>

60

<Menubar.SubContent>

61

<Menubar.Item>Format Document</Menubar.Item>

62

<Menubar.Item>Check Spelling</Menubar.Item>

63

</Menubar.SubContent>

64

</Menubar.Portal>

65

</Menubar.Sub>

66

);

67

}

68

```

69

70

### MenubarSubTrigger (SubTrigger)

71

72

Trigger that opens a submenu when hovered or activated, typically displaying an arrow indicator.

73

74

```typescript { .api }

75

/**

76

* Trigger that opens a submenu on hover or activation

77

* @param props - Sub-trigger props

78

* @returns JSX element representing the submenu trigger

79

*/

80

function MenubarSubTrigger(props: MenubarSubTriggerProps): React.ReactElement;

81

82

interface MenubarSubTriggerProps extends React.ComponentPropsWithoutRef<'div'> {

83

/** Whether the trigger is disabled */

84

disabled?: boolean;

85

/** Text value for accessibility */

86

textValue?: string;

87

}

88

```

89

90

**Usage Examples:**

91

92

```typescript

93

// Basic sub-trigger with arrow

94

<Menubar.SubTrigger>

95

Recent Files

96

<ChevronRightIcon className="sub-arrow" />

97

</Menubar.SubTrigger>

98

99

// Sub-trigger with custom content

100

<Menubar.SubTrigger>

101

<FileIcon />

102

<span>Export Options</span>

103

<ArrowIcon />

104

</Menubar.SubTrigger>

105

106

// Disabled sub-trigger

107

<Menubar.SubTrigger disabled>

108

Advanced Tools

109

<ChevronRightIcon />

110

</Menubar.SubTrigger>

111

```

112

113

### MenubarSubContent (SubContent)

114

115

Content container for submenu items, positioned relative to the sub-trigger.

116

117

```typescript { .api }

118

/**

119

* Content container for submenu items

120

* @param props - Sub-content props

121

* @returns JSX element representing the submenu content

122

*/

123

function MenubarSubContent(props: MenubarSubContentProps): React.ReactElement;

124

125

interface MenubarSubContentProps extends React.ComponentPropsWithoutRef<'div'> {

126

/** Alignment relative to trigger */

127

align?: 'start' | 'center' | 'end';

128

/** Side preference for positioning */

129

side?: 'top' | 'right' | 'bottom' | 'left';

130

/** Distance from trigger in pixels */

131

sideOffset?: number;

132

/** Alignment offset in pixels */

133

alignOffset?: number;

134

/** Whether content should avoid collisions with the boundary */

135

avoidCollisions?: boolean;

136

/** Element or area to constrain positioning within */

137

collisionBoundary?: Element | null | Array<Element | null>;

138

/** Padding from collision boundary in pixels */

139

collisionPadding?: number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>;

140

/** Whether content should stick to trigger when boundary is reached */

141

sticky?: 'partial' | 'always';

142

/** Callback fired when content loses focus */

143

onCloseAutoFocus?: (event: Event) => void;

144

/** Callback fired when escape key is pressed */

145

onEscapeKeyDown?: (event: KeyboardEvent) => void;

146

/** Callback fired when pointer moves outside */

147

onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;

148

/** Callback fired when focus moves outside the content */

149

onFocusOutside?: (event: FocusOutsideEvent) => void;

150

/** Callback fired when interaction occurs outside the content */

151

onInteractOutside?: (event: InteractOutsideEvent) => void;

152

}

153

```

154

155

**Usage Examples:**

156

157

```typescript

158

// Basic sub-content

159

<Menubar.SubContent>

160

<Menubar.Item>Export as PDF</Menubar.Item>

161

<Menubar.Item>Export as PNG</Menubar.Item>

162

<Menubar.Item>Export as SVG</Menubar.Item>

163

</Menubar.SubContent>

164

165

// Sub-content with custom positioning

166

<Menubar.SubContent

167

side="right"

168

align="start"

169

sideOffset={2}

170

>

171

<Menubar.Item>Option 1</Menubar.Item>

172

<Menubar.Item>Option 2</Menubar.Item>

173

</Menubar.SubContent>

174

175

// Sub-content with nested submenus

176

<Menubar.SubContent>

177

<Menubar.Item>Quick Export</Menubar.Item>

178

179

<Menubar.Sub>

180

<Menubar.SubTrigger>

181

Advanced Export

182

<ChevronRightIcon />

183

</Menubar.SubTrigger>

184

<Menubar.Portal>

185

<Menubar.SubContent>

186

<Menubar.Item>With Metadata</Menubar.Item>

187

<Menubar.Item>Compressed</Menubar.Item>

188

</Menubar.SubContent>

189

</Menubar.Portal>

190

</Menubar.Sub>

191

</Menubar.SubContent>

192

```

193

194

**Key Implementation Details:**

195

196

- **Data Attributes**:

197

- SubTrigger automatically receives `data-radix-menubar-subtrigger=""` attribute

198

- SubContent automatically receives `data-radix-menubar-content=""` attribute

199

- **CSS Custom Properties**: SubContent exposes the same positioning variables as MenubarContent:

200

- `--radix-menubar-content-transform-origin`: Transform origin for animations

201

- `--radix-menubar-content-available-width`: Available width for content

202

- `--radix-menubar-content-available-height`: Available height for content

203

- `--radix-menubar-trigger-width`: Width of the triggering element

204

- `--radix-menubar-trigger-height`: Height of the triggering element

205

- **Navigation Behavior**: Arrow keys navigate horizontally between main menu triggers, preventing submenu opening

206

207

### MenubarArrow (Arrow)

208

209

Optional arrow pointing from trigger to content, providing visual connection between trigger and dropdown.

210

211

```typescript { .api }

212

/**

213

* Optional arrow pointing from trigger to content

214

* @param props - Arrow props

215

* @returns JSX element representing the arrow

216

*/

217

function MenubarArrow(props: MenubarArrowProps): React.ReactElement;

218

219

interface MenubarArrowProps extends React.ComponentPropsWithoutRef<'svg'> {

220

/** Width of the arrow in pixels (default: 10) */

221

width?: number;

222

/** Height of the arrow in pixels (default: 5) */

223

height?: number;

224

}

225

```

226

227

**Usage Examples:**

228

229

```typescript

230

// Basic arrow

231

<Menubar.Portal>

232

<Menubar.Content>

233

<Menubar.Arrow />

234

<Menubar.Item>Menu Item</Menubar.Item>

235

</Menubar.Content>

236

</Menubar.Portal>

237

238

// Custom sized arrow

239

<Menubar.Portal>

240

<Menubar.Content>

241

<Menubar.Arrow width={12} height={6} />

242

<Menubar.Item>Menu Item</Menubar.Item>

243

</Menubar.Content>

244

</Menubar.Portal>

245

246

// Styled arrow

247

<Menubar.Portal>

248

<Menubar.Content>

249

<Menubar.Arrow className="custom-arrow" />

250

<Menubar.Item>Menu Item</Menubar.Item>

251

</Menubar.Content>

252

</Menubar.Portal>

253

```

254

255

## Complex Submenu Examples

256

257

### Multi-Level Menu Structure

258

259

```typescript

260

function MultiLevelMenu() {

261

return (

262

<Menubar.Content>

263

<Menubar.Item>New File</Menubar.Item>

264

<Menubar.Item>Open</Menubar.Item>

265

266

<Menubar.Sub>

267

<Menubar.SubTrigger>

268

Recent Files

269

<ChevronRightIcon />

270

</Menubar.SubTrigger>

271

<Menubar.Portal>

272

<Menubar.SubContent>

273

<Menubar.Item>document1.txt</Menubar.Item>

274

<Menubar.Item>project.json</Menubar.Item>

275

276

<Menubar.Sub>

277

<Menubar.SubTrigger>

278

More Files

279

<ChevronRightIcon />

280

</Menubar.SubTrigger>

281

<Menubar.Portal>

282

<Menubar.SubContent>

283

<Menubar.Item>old-project.js</Menubar.Item>

284

<Menubar.Item>backup.sql</Menubar.Item>

285

</Menubar.SubContent>

286

</Menubar.Portal>

287

</Menubar.Sub>

288

</Menubar.SubContent>

289

</Menubar.Portal>

290

</Menubar.Sub>

291

292

<Menubar.Separator />

293

294

<Menubar.Sub>

295

<Menubar.SubTrigger>

296

Export

297

<ChevronRightIcon />

298

</Menubar.SubTrigger>

299

<Menubar.Portal>

300

<Menubar.SubContent>

301

<Menubar.Item>Export as PDF</Menubar.Item>

302

<Menubar.Item>Export as HTML</Menubar.Item>

303

304

<Menubar.Sub>

305

<Menubar.SubTrigger>

306

Export as Image

307

<ChevronRightIcon />

308

</Menubar.SubTrigger>

309

<Menubar.Portal>

310

<Menubar.SubContent>

311

<Menubar.Item>PNG</Menubar.Item>

312

<Menubar.Item>JPEG</Menubar.Item>

313

<Menubar.Item>SVG</Menubar.Item>

314

</Menubar.SubContent>

315

</Menubar.Portal>

316

</Menubar.Sub>

317

</Menubar.SubContent>

318

</Menubar.Portal>

319

</Menubar.Sub>

320

</Menubar.Content>

321

);

322

}

323

```

324

325

### Dynamic Submenu Content

326

327

```typescript

328

function DynamicSubmenu() {

329

const [recentFiles, setRecentFiles] = React.useState([

330

"document1.txt",

331

"project.json",

332

"notes.md"

333

]);

334

335

return (

336

<Menubar.Sub>

337

<Menubar.SubTrigger>

338

Recent Files ({recentFiles.length})

339

<ChevronRightIcon />

340

</Menubar.SubTrigger>

341

<Menubar.Portal>

342

<Menubar.SubContent>

343

{recentFiles.length > 0 ? (

344

recentFiles.map((file, index) => (

345

<Menubar.Item

346

key={file}

347

onSelect={() => openFile(file)}

348

>

349

{file}

350

</Menubar.Item>

351

))

352

) : (

353

<Menubar.Label>No recent files</Menubar.Label>

354

)}

355

356

{recentFiles.length > 0 && (

357

<>

358

<Menubar.Separator />

359

<Menubar.Item onSelect={() => setRecentFiles([])}>

360

Clear Recent Files

361

</Menubar.Item>

362

</>

363

)}

364

</Menubar.SubContent>

365

</Menubar.Portal>

366

</Menubar.Sub>

367

);

368

}

369

```

370

371

## Type Definitions

372

373

```typescript { .api }

374

// Element reference types

375

type MenubarSubTriggerElement = HTMLDivElement;

376

type MenubarSubContentElement = HTMLDivElement;

377

type MenubarArrowElement = SVGSVGElement;

378

379

// Event types

380

interface PointerDownOutsideEvent {

381

target: HTMLElement;

382

preventDefault(): void;

383

}

384

385

interface FocusOutsideEvent {

386

target: HTMLElement;

387

preventDefault(): void;

388

}

389

390

interface InteractOutsideEvent {

391

target: HTMLElement;

392

preventDefault(): void;

393

}

394

```