Page and layout

Fold

A card that opens: setup, extras, the danger zone. It says what is inside and how much.

openvibe-shared/kitJSON

Build it

kit.fold({ title: 'Put this room on your site', meta: 'two ways', body: kit.snippet({ code: '<iframe src="https://openvibe.chat/r/night-owls/embed"></iframe>' }) }) + kit.fold({ title: 'Delete the room', tone: 'danger', body: kit.notice({ kind: 'warn', text: 'Everyone loses the history. This cannot be undone.' }) })

Props

id
any?
body
string?
icon
string?
meta
string?
open
boolean?
tone
string?
title
string?

Anatomy

  • summarythe title, a muted word after it, a chevron that turns; minHeight size.tap; weight type.weight.bold
  • bodypadding space.md

Behaviour and access

Keyboard
Standard focus order
Outside tap
Leaves it open
Scroll
The page still scrolls
Reduced motion
Remove movement; keep the state change.
Name
its title
Target
44 px
Focus
Enter or Space opens and closes

States

  • closed
  • open
  • focused

Tokens

  • size.tap
  • type.weight.bold
  • space.md

Android · Jetpack Compose

Surface

@Composable fun OvFold(title: String, meta: String? = null, initiallyOpen: Boolean = false, content: @Composable () -> Unit) { var open by rememberSaveable { mutableStateOf(initiallyOpen) } Surface(shape = RoundedCornerShape(OvScale.RadiusMd)) { Column { Row(Modifier.fillMaxWidth().heightIn(min = OvScale.SizeTap).clickable(onClickLabel = if (open) "Close" else "Open") { open = !open }.padding(horizontal = OvScale.SpaceMd), verticalAlignment = Alignment.CenterVertically) { Text(title, Modifier.weight(1f), fontWeight = FontWeight.Bold) if (meta != null) Text(meta, color = MaterialTheme.colorScheme.onSurfaceVariant) Icon(Icons.Default.ExpandMore, null, Modifier.rotate(if (open) 180f else 0f)) } AnimatedVisibility(open) { Box(Modifier.padding(OvScale.SpaceMd)) { content() } } } } }

iOS · SwiftUI

struct OvFold<Content: View>: View { let title: String var meta: String? = nil @State var open = false @ViewBuilder var content: () -> Content var body: some View { DisclosureGroup(isExpanded: $open) { content().padding(.top, OvScale.spaceSm) } label: { HStack { Text(title).fontWeight(.bold); if let meta { Text(meta).foregroundStyle(OvTokens.colorTextMuted) } }.frame(minHeight: OvScale.sizeTap) } } }

Markup

Default

<details class="ovk-fold"><summary><span>Put this room on your site <span class="ovk-muted">· two ways</span></span><svg class="ovk-i" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m9.9 6.6 6.6 6.6 -6.6 6.6"/></svg></summary><div class="ovk-fold-body"><div class="ovk-snip"><button type="button" class="ovk-icon-btn" data-ovk-copy="#ovk-snip-319-64" aria-label="Copy" title="Copy" hidden><svg class="ovk-i" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg></button><pre tabindex="0" id="ovk-snip-319-64"><code>&lt;iframe src=&quot;https://openvibe.chat/r/night-owls/embed&quot;&gt;&lt;/iframe&gt;</code></pre></div></div></details><details class="ovk-fold ovk-fold--danger"><summary><span>Delete the room</span><svg class="ovk-i" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m9.9 6.6 6.6 6.6 -6.6 6.6"/></svg></summary><div class="ovk-fold-body"><p class="ovk-notice ovk-notice--warn" role="status"><svg class="ovk-i" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4 2.8 19.5h18.4Z"/><path d="M12 10v4M12 17h.01"/></svg><span>Everyone loses the history. This cannot be undone.</span></p></div></details>