Styles

A perspective says which statements to show. A stylesheet says how they are laid out and drawn: what stands on a line, what is nested and indented, where captions go, how wide things are. Every ontology and every topic can carry styles of its own, edited in the app through the built-in Styles view, and your profile can carry styles that apply everywhere you go.

Who this page is for. The built-in styles are designed to cover what a regular RDF analyst or data author needs, and most people never change them. This page is for power users, preferably with some programming background, who want a view to look different from the default. If you know CSS, much of it will feel familiar: a style has a selector saying what it applies to and a set of properties it sets, and several styles can apply to the same thing.

What styles are for, and what they are not

Styling here means what the word has always meant: changing how information looks. A style decides what stands on a line, where captions go, how much room things take, which colours and marks they wear. It does not decide what information there is, and it does not decide what you can do with it. When you adjust styles you are adjusting the look of a view, not building a user interface.

Three things shape what you see, and each has one job:

WhoDecides
The perspectiveWhich statements are listed and nested, which parts are shown and editable, and what is allowed: whether a list takes new rows, whether rows can be deleted, what a click on a part does.
StylesHow all of that is laid out and drawn.
The appHow it behaves: clicks and menus, editing, the actions — adding, copying, deleting — paging, opening a record, undo. The same in every view.

The last row is deliberate. Because behaviour belongs to the app and not to the view, a perspective you have never seen before works the way every other one does. A style can change how the page control looks, but not whether it is there or what it does; the actions it does not meet at all. A style can look different while a row is open, selected or being edited (av:expanded, av:selected and av:edited), the way a CSS rule can for :hover; but nothing in a stylesheet makes anything happen, and nothing in one can take away something the perspective allows.

If what you want is an interface of your own — your own buttons, custom controls, things that happen on a click — styles are the wrong tool, on purpose. A scripting facility for that is planned as a separate part of Arspec. It will be kept apart from perspectives and styles, so that these two stay what they are: plain descriptions that are safe to open and to share.

Where styles come from

Styles reach a view from four places, and when two of them disagree the one ranked higher wins:

Ranked asKept inWho sees it
Your stylesheetsYour profileYou, in every file, on every machine your profile follows you to.
Topic stylesThe topic's .arspec fileAnyone you hand the topic to. The ontology files stay untouched.
In-file stylesThe ontology file itselfAnyone who opens the file. The look ships with the data.
App defaultsThe appEveryone, always, underneath everything else.

That is the default ranking, and it is a preference rather than a rule: Preferences → Profile → Styling → Style priority lets you rearrange it, so you can decide that a file's own styling beats yours or the other way round. This is one place Arspec deliberately differs from CSS, where the reader gets no say in how the sources rank.

Editing styles in the app

The Styles view is a built-in perspective that shows the styles a file carries, one record per style, and lets you edit them in place. It is off by default, because most files carry no styles. Switch it on from the perspectives screen (the gear at the end of the tab strip) for whichever file you want to style:

In the view, adding a row — Add to list on the list’s menu, Insert on a style’s (above it), or the action bar’s Add — makes a new style with a Label (a name, for you) and an Order (its place in the file; later styles override earlier ones). Right-click a style to move it up or down. Under Selector, add one row per condition the style requires. A new row starts as av:type av:Record: change it to the condition you mean, from the av: namespace, and give it its value. Under Set Attributes, add one row per property the style sets. A new row starts with the av: namespace chosen and the name left for you to pick. Where a style sets av:content, the items it holds are listed under that row in their order: right-click an item to move it up or down or to add one above it, and click an item to see and edit what it is, shown through the built-in Attributes+. Both lists are described below. A style can also be written by hand in any text editor; it is a few lines of Turtle.

The Styles view of a file carrying two styles: each is a record with a Label, a Selector listing its conditions, and Set Attributes listing what it sets.
The Styles view on a file with two styles of its own. Each has a Label, a Selector and the attributes it sets.

What a style can target

A perspective defines a set of hierarchical lists (represented in style selectors under the element av:Source) containing individual matched statements (represented as av:Record). The app builds those two levels from the perspective automatically. Everything else you see inside a record, such as the line its parts stand on, the caption before a value and the block of nested lists under a row, is put there by a style. The built-in stylesheet puts it there by default, and any style of yours can change or replace it.

A style applies to entries, and every entry is exactly one element. Below is the list of all the potential targets whose presentation can be altered by a style:

ElementWhat it is
av:SourceOne list of a perspective, top-level or nested. The top-level list is the one av:root true finds.
av:RecordOne row of a list, that is, one matched statement. Everything inside a record repeats for every row.
av:TermOne displayed part of a row: its subject, its relation or its value.
av:LayoutA container a style has added: a line of fields, a form, a grid.
av:MainThe record's row: what it shows of its own statement. Every record has one.
av:DetailsWhat hangs under the row: the nested lists, or the frame of lenses when the record is expanded. Every record has one, and how far it is indented is its own av:paddingStart.
av:TextA piece of fixed text, or the list's own label placed as a cell.

The elements are a fixed set, and an entry’s element is fixed too: it is decided once, when the entry is created — by the app for lists, records and the page control, by the content item for everything else — and nothing changes it afterwards. No style can add an element to an entry or take one away, and it is the same in every state. It is the one thing about an entry you can always rely on. The open-ended way to mark things for styling is extra types: further classes on a content item, or on a list, described further down.

The shape of a style

A stylesheet is a Turtle file, or a part of one. Each style is an av:Style node with a label, an order, a selector and the properties it sets. Here is one from the built-in sheet:

@prefix av:   <https://arspec.org/ars/spec/1/schema/view#> .
@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

[ a av:Style ] rdfs:label "a predicate in a line keeps to its cap" ;
    av:order 2 ;
    av:select [ a av:Selector ;
        av:element av:Term ; av:role rdf:predicate ; av:parentType av:Line ] ;
    av:set [ av:maxWidth "150px" ] .
PartSays
rdfs:labelThe style's name, for people reading the sheet.
av:orderIts position in the sheet. Later styles override earlier ones, so this is also its rank among the styles in the same sheet. It is a decimal number: the app writes 0, 1, 2 and so on, and you can insert a style at 1.5 without renumbering.
av:selectThe selector: the conditions an entry must meet for the style to apply. Leave it out and the style applies to everything.
av:setWhat the style sets: any of the properties, one per line, and — for a list, a row, a record or its details — its content, all on this one node.

Selectors

A selector is a list of conditions. Every condition stated must hold; a condition not stated is not checked. There is no or: if a style should apply in two different situations, write it twice. A condition from this vocabulary that the app does not know — a misspelt one, one from a newer version, one since retired such as av:maxDepth — switches the whole style off, because ignoring it would make the style apply in more places than you wrote it for.

ConditionHolds when
What the entry is
av:elementThe entry is that element (see the table above).
av:roleThe entry is a term showing that part of the statement: rdf:subject, rdf:predicate or rdf:object.
av:typeThe entry is typed with that class: its element, or one of the extra types its item was given. Extra types are class names you choose yourself; see below.
Where it stands
av:parentElement, av:parentTypeThe entry's immediate container is that element, or is typed with that class. Only one step up.
av:roottrue: the entry belongs to the perspective’s top-level list — the list itself, its rows, and everything in them that is not a nested list. false: it belongs to a nested list. It is about which list, never how deep: drawing a box round the rows, or round a row, changes neither answer. A perspective shown through an invocation is a view of its own, and its top-level list is the root of that one.
Which list it belongs to
av:sourceIdThe nearest list is the one with that ID. The way to style one particular list by name.
av:sourceTypeThe nearest list declares that class among its Extra types. Works like a CSS class; see below.
av:showsCountThe nearest list displays exactly that many parts of each statement. Distinguishes a single field from a line of several.
av:maxCount, av:minCountThe nearest list's ceiling, or its floor, is exactly that number: av:maxCount 1 is a list meant to hold one record. A list with no ceiling matches no av:maxCount; a list that states no floor has one of 0.
av:hasLabelWhether the nearest list has a label: true or false. What lets a style dress the labelled lists, or the unlabelled ones, without naming each.
What the statement says
av:recordSubject, av:recordPredicate, av:recordObjectThe statement the entry is drawn for — the nearest record's — has that subject, predicate or object: an IRI, or a literal by its text. True of the record and of everything inside it, nested lists included, until a nested record begins.
What is happening right now
av:editedThe term's editor is open. True of that one field only.
av:selectedThe entry is the selected record or list, or one of its own parts: a record’s row and the fields in it, a list’s heading. It stops at whatever is nested: the rows of a selected list are not selected. False for everything while the view as a whole is selected.
av:expandedThe entry is inside a record the user has expanded, or on a record's own page. True of the record and of everything inside it; add av:element to mean just the record.

The conditions in the first three groups describe the structure of the view, which is the same for every row. The last two groups describe one row: what its statement says, and what it is doing at the moment — and the latter is how a view can change shape while it is being used, for instance by giving a field more room only while you are typing in it.

Which style wins

Several styles will usually apply to one entry. Each property is resolved separately, and the highest-ranked style that sets it wins. Unlike CSS, there is no specificity: a selector only decides whether a style applies, and a longer selector carries no extra weight. The ranking is:

  1. Where the sheet comes from: your stylesheets, the topic's, the file's own, the app's defaults, in the order set under Preferences → Profile → Styling → Style priority.
  2. Own before imported. Within one of those, a file's own styles beat the styles of files it imports.
  3. Position in the sheet. Within one sheet, the style with the higher av:order wins, so a sheet reads top to bottom with refinements after the basics.
  4. What the entry itself states, if anything, wins over every style.

Two consequences worth knowing. First, because resolution is per property, a style that sets av:rowGap leaves av:paddingStart to whoever set it, so you can override one thing without restating the rest. Second, a property that no style sets takes the default given in the vocabulary, and defaults are never written to a file.

Properties a style can set

Every property is independent and optional. Lengths and track lists are written as strings; named choices are terms from the av: namespace.

Lengths

One way of writing a length, used for widths, heights, gaps, padding and grid tracks alike:

WrittenMeans
autoAs big as its content.
120pxA fixed size in logical pixels, which the theme scales.
50%A fraction of the container's inner size on that axis.
1frA share of whatever room is left once everything else has taken its size, as in CSS grid.
80px..30%A range: any of the above, clamped between a minimum and a maximum.
relatedA spacing token the theme resolves to a size: none, tight, related, unrelated, section, indent.

Prefer the tokens for gaps and padding. They say what the spacing means rather than how many pixels it is, so a sheet reads right on any screen and a density setting rescales a whole view at once. Sides are named start and end rather than left and right, so right-to-left languages work without changes.

An entry's own size and placement

PropertySaysDefault
av:width, av:heightIts size, as a length.auto
av:minWidth, av:maxWidth
av:minHeight, av:maxHeight
Limits on its size. A share (1fr) with a maximum stops growing at the maximum, and the room it does not take goes to the other shares.none
av:paddingStart, av:paddingTop, av:paddingEnd, av:paddingBottomRoom inside its own edge. A nested list is indented by its padding.none
av:justifySelf, av:alignSelfHow it sits in the room it is given, horizontally and vertically: av:start, av:center, av:end, av:stretch, av:baseline.the container's setting
av:widthGroup, av:heightGroupA size group it belongs to, for aligning things into columns.—
av:gridColumn, av:gridRowWhere it stands in its container's grid: "2", "1..3", "span 2".placed automatically
av:gridAreaA named area from the container's av:areas. Takes precedence over the two above.—
av:drawOrderIts order among its siblings when drawn. (Not av:order, which orders styles and items as written.)as written
av:x, av:yIts position inside a canvas container. Ignored elsewhere.—

There are no margins. An entry never pushes its neighbours; all spacing belongs to the container, through gaps and its own padding.

A container's layout of its children

Every container is a grid, and a row or a stack is simply a grid with one track.

PropertySaysDefault
av:directionThe axis children are laid along: av:column or av:row.av:column, a stack
av:columns, av:rowsTrack sizes, separated by spaces: "auto 1fr", or "auto-fill(120px)" for as many as fit.one auto column
av:rowGap, av:columnGapSpace between children on each axis.related
av:justify, av:alignHow children sit in their cells when they do not say themselves.stretch / start
av:areasNamed cells, one name per cell with rows separated by " / ": "header header / nav main". A name repeated across neighbouring cells spans them.—
av:layoutThe kind of container: av:grid, av:canvas, or an IRI of your own. An unknown kind is drawn as a grid.av:grid
av:reverseWhether children are laid from the end.false

The one grid covers most layouts you would want: av:direction av:row is a line, av:columns "auto 1fr" is a label-and-value form, av:columns "auto-fill(12em)" wraps items, and two children sharing one cell overlay each other.

Text and decoration

PropertySaysDefault
av:labelPlacementWhere the entry's label is drawn: av:heading above its content, av:leading before it on the same line, or av:none.av:heading
av:labelRoleThe text role the label is drawn in: av:heading, av:caption, av:body.av:heading
av:textRoleThe text role the entry's own text is drawn in: av:body, av:heading, av:caption, or av:code for text the theme should set in its code face.av:body
av:colorThe colour the entry's text is drawn in — its caption along with its value.the role's own
av:backgroundA colour behind the entry, filling the room its geometry gave it.no box at all
av:bold, av:italicWhether the text is emphasised: true or false. Stated false they take the emphasis off text whose role carries one.the role's own
av:decorationThe line drawn on the text: av:underline, av:lineThrough or av:none.the role's own
av:connectorWhether a line is drawn from a record's main term to the things nested under it: av:line or av:none.av:none
av:frameColorThe colour of the frame drawn round the selected record or list: a theme role, or a literal — as every colour is, below. Set it on records, on lists, or on both.av:primary
av:frameWidth, av:frameCornerThe frame's thickness and corner radius, in logical pixels.1, 1
av:frameFitHow the frame wraps the things nested under the row: av:hug follows them, av:record spans the record's full width.av:hug

A sheet names roles, never fonts: av:caption says what a piece of text is, and the theme decides what a caption looks like, in light and dark alike. There is deliberately no font family and no font size — a size would also be measured into the av:widthGroup caps that keep columns in line, so a style that set one would quietly break them. If you want bigger text, name a role that is bigger.

Colours are named the same way. A colour is a theme role — an accent (av:primary, av:secondary, av:tertiary, av:error), an edge (av:outline, av:outlineVariant), a surface something may stand on (av:surface, av:primaryContainer and its fellows), or the colour text takes standing on one of those (av:onSurface, av:onSurfaceVariant, av:onPrimaryContainer, …) — or a literal "#RRGGBB" / "#AARRGGBB" where nothing in the theme is the colour meant. Prefer the role: a sheet written in hex is a sheet that is wrong in half the themes.

A mark is the entry's own, and nothing inherits it. Colouring a line does not colour the terms standing in it; say what you mean about those, and av:parentType is how a style names them. The marks on a term reach its caption and its stored text — not the type tag beside it, and not what a search picked out of it: those belong to the cell, not to the style.

A property that is not in the vocabulary is not an error; it is kept and written back untouched, in case a future version or another tool understands it.

Content: what stands in a list and in a record

The same styles that say how things look also say what stands where. That is still presentation: a style arranges the information the perspective provides, and whatever text you add round it, and nothing it places does anything. Every list and every record is a small tree the app builds for you and a style fills in: a list (av:Source) holds its records, and a record holds a Main — its row — and a Details — what hangs under the row, where its nested lists live. These four always exist. What each contains is stated with one property, av:content:

Onav:content states
a list (av:Source)Where its records go, and anything shown once per list round them: a line of text over the rows, a note under them, a box round the rows alone.
a row (av:Main)Which parts of the statement stand on it, and how they are arranged. Most of what a record looks like is decided here.
a record’s details (av:Details)What hangs under the row, beside the nested lists that are always there.
a record (av:Record)Where its Main and its Details stand, and anything beside them. Rarely needed — unless you do without the two.

The Main and the Details are folders of convenience. They are where the app's own styles put things, and they bring what makes the conventional layout cheap to state: the Main is the row a record is captioned by and framed round, and the Details holds every nested list without a style having to name one, indented as details are. You are not bound to them. A nested list can be placed anywhere in the record — by id, or all of them at once — and a style may put everything on the record itself and leave the two empty; an empty folder stands nothing. The built-in styles assume the convention, so going without it means stating more yourself, and we do not recommend it; but nothing stops you.

The content is a list of av:items, ordered by av:order. An item is typed by what it makes — the same word a selector asks for with av:element:

ItemMakes
av:TermOne part of the statement; av:role says which. In a record only: a list has no row of its own, so in a list’s content a term makes nothing.
av:LayoutA container around a list of its own: its items stand under an av:content, the same way a style's do, so a list reads the same at every depth. Any further class after the a (a av:Layout, av:Line) types it for selectors — and names it, unless av:id says otherwise.
av:TextFixed text — or, with av:labelled, the list’s own label as a cell that can be sized and placed like a field.

Four more items make nothing: they place something the tree already has. A style cannot remove what they place — leave a marker out, and what it would have placed stands at the end anyway.

MarkerPlaces
av:RecordsThe list’s records, in the list: among whatever else the list shows once, or inside an av:Layout of the list’s own — a box round the rows, with the rest of the list outside it.
av:Main, av:DetailsThe record’s row and its details, in the record.
av:SourcesEvery nested list not yet placed — in the details by convention, but anywhere in the record.
av:Source + av:sourceIdOne nested list, or one invocation, by the id it was declared with — anywhere in the record. Placed, it is out of the details. Markers take their turn in content order, the record's before the row's before the details', so one named after av:Sources finds nothing left.

Two rules make this predictable:

Coordinating across a view

A style cannot read what another style decided. Everything that has to be coordinated between two places goes through one of a few named channels.

av:type: a class you choose

A content item is typed with its element — a av:Layout — and it can be typed with more: a av:Layout, av:Line. Every class after the element is an extra type, and the entry the item produces keeps them all. A selector matches one with av:type, or matches a container’s with av:parentType. This is the equivalent of a CSS class, written as the RDF class it is: an IRI, so two stylesheets cannot collide on a name, and you can declare and describe your own in your own namespace. The built-in style that builds a record’s line types it av:Line, and the styles that size the fields inside a line match that type without needing to know which style built the line or how deep it sits:

# one style builds the line and types it …
[ a av:Style ] rdfs:label "a record is a line of its shown parts" ;
    av:select [ a av:Selector ; av:element av:Main ] ;
    av:set [ av:content [
        av:item [ a av:Layout, av:Line ; av:order 0 ;
            av:content [
                av:item [ a av:Term ; av:order 0 ; av:role rdf:subject ] ;
                av:item [ a av:Term ; av:order 1 ; av:role rdf:predicate ] ;
                av:item [ a av:Term ; av:order 2 ; av:role rdf:object ] ] ] ] ] .

# … and two others find it by the type.
[ a av:Style ] rdfs:label "a line is a row" ;
    av:select [ a av:Selector ; av:type av:Line ] ;
    av:set [ av:direction av:row ] .

[ a av:Style ] rdfs:label "the fields of a line share it" ;
    av:select [ a av:Selector ; av:element av:Term ; av:parentType av:Line ] ;
    av:set [ av:width "1fr" ] .

Like the element, an entry’s types are fixed when the entry is created and never change: no style adds a type to an existing entry, and nothing about the current state alters one. An item typed with two elements at once (a av:Term, av:Text) is read as the plainer of the two — the order is Text, Layout, Term, Records, Sources, Main, Details, Source — and the other is dropped.

A class also names what it types. Every entry has an id, and ids are what the app keeps your open editors and expanded records by while a view is rebuilt; a container or a text cell takes its name from its own class, so the line above stands as line without being told to. Two items landing on one name are numbered apart, and nothing a style can write makes two entries with the same id. Write av:id when you would rather pin the name to something of your own choosing than to the class.

av:widthGroup and av:heightGroup: one size, shared

Every entry that names the same size group is made as wide (or as tall) as the widest of them, anywhere in the view, whether or not they are siblings or even come from the same list. This is how a tree of nested lists can line up like a table:

[ a av:Style ] rdfs:label "predicates line up across the whole view" ;
    av:select [ a av:Selector ; av:element av:Term ; av:role rdf:predicate ] ;
    av:set [ av:widthGroup "predicate" ; av:justifySelf av:start ] .

A group name is global to the view, so name groups for what they mean ("predicate", "label") rather than for where they happen to be used.

av:sourceType: a class declared in the perspective

A list in a perspective can declare extra types under Configuration → Extra types. A style matches one with av:sourceType, and the match holds for that list, its records and everything inside them, stopping where a nested list begins. The perspective says which look it wants; the stylesheet says what that look is. This is the same division of labour as class names in a CSS framework.

The app ships one such type, av:Form: the list draws no heading, and each record becomes one line led by the list's name where the relation would have been. A nested list pinned to one relation then reads as Label value and lines up with its siblings. It takes effect on a list with a label and a ceiling of one, the lists it is for, and changes nothing elsewhere. You can key a style on any IRI you like, and a sheet ranked above the app's may redefine what av:Form means.

av:sourceId: one list, by name

The direct route: address exactly one list of one perspective. Useful, but worth reaching for last, because a style keyed on an ID says nothing about why it applies and stops working the moment the perspective is copied. A type usually says what you meant.

av:edited, av:expanded and av:selected: the current moment

These three conditions describe what one row is doing right now rather than how the view is built, and they are how a view changes shape as it is used. The built-in sheet uses the first, in a pair of styles that is also the clearest example of the ranking at work:

[ a av:Style ] rdfs:label "a predicate in a line keeps to its cap" ;
    av:order 2 ;
    av:select [ a av:Selector ;
        av:element av:Term ; av:role rdf:predicate ; av:parentType av:Line ] ;
    av:set [ av:maxWidth "150px" ] .

[ a av:Style ] rdfs:label "a predicate being typed into gets room" ;
    av:order 3 ;
    av:select [ a av:Selector ;
        av:element av:Term ; av:role rdf:predicate ; av:edited true ] ;
    av:set [ av:maxWidth "320px" ] .

The width a relation normally gets is enough for reading a name but not for typing one, since an open editor holds two fields and a button. Both styles set the same property, so the ranking decides between them: while the editor is open the second one is later in the sheet and wins, and the field may grow to 320px. The moment the editor closes, the second style stops matching and the 150px cap is back.

false is a condition too: av:edited false means “while this is not being edited”. av:expanded is true of an expanded record and of everything inside it; add av:element av:Record when you mean the record itself. av:selected is narrower on purpose: it is true of the one record or list the cursor is on and of its own parts, and not of what is nested in it, so av:element av:Record ; av:selected true is exactly the record with the frame round it, and av:element av:Term ; av:selected true the fields of that one row.

av:recordSubject, av:recordPredicate, av:recordObject: what the statement says

Three conditions read the data itself: the subject, the predicate and the object of the statement a record is drawn for. A value is an IRI, written as one (rdfs:label, ex:alice), or a literal, written in quotes and matched by its text alone — "42" is the typed 42 and the plain one alike. The condition holds of the record and of everything inside it, a nested list included, and stops where a nested record begins, which has a statement of its own. Like av:edited, this is a condition on one row rather than on the view's structure: a style stating it applies to alice's record and not to bob's.

[ a av:Style ] rdfs:label "a label reads quieter" ;
    av:select [ a av:Selector ;
        av:element av:Term ; av:role rdf:object ; av:recordPredicate rdfs:label ] ;
    av:set [ av:color av:onSurfaceVariant ; av:italic true ] .

A blank node has no name a style could write, and matches nothing. And there is no pattern and no comparison beyond equality: for “every statement a query would select”, give those statements a list of their own — a nested list with a filter — and key the style on its av:sourceId or av:sourceType.

Recipes

Tighten a nested list

[ a av:Style ] rdfs:label "attributes sit close together" ;
    av:order 0 ;
    av:select [ a av:Selector ; av:element av:Source ; av:sourceId "props" ] ;
    av:set [ av:rowGap "tight" ; av:labelPlacement av:none ] .

A label-and-value form instead of a line

[ a av:Style ] rdfs:label "two columns, label then value" ;
    av:order 1 ;
    av:select [ a av:Selector ; av:type av:Line ] ;
    av:set [ av:direction av:column ; av:columns "auto 1fr" ; av:align av:baseline ] .

A quieter frame on the selected record

[ a av:Style ] rdfs:label "the selected record is outlined, not accented" ;
    av:order 2 ;
    av:select [ a av:Selector ; av:element av:Record ] ;
    av:set [ av:frameColor av:outlineVariant ; av:frameCorner "6" ;
        av:frameFit av:record ] .

Mark a kind of statement

[ a av:Style ] rdfs:label "inferred statements read quieter" ;
    av:order 3 ;
    av:select [ a av:Selector ; av:element av:Term ; av:sourceId "inferred" ] ;
    av:set [ av:color av:onSurfaceVariant ; av:italic true ] .

Where what you mean is a part of the statement — every rdfs:comment, everything about one subject — av:recordPredicate and its two siblings say it directly. Where it is more than a part can say — every statement a query would select — the list is the data-dependent half: a child datasource filtered to the statements you mean gives them an av:sourceId, or a type on what its rows put on screen gives them an av:type, and either is a name a style can catch. Because marks do not inherit, this one names the terms themselves rather than the row around them.

Draw a line to the nested lists

[ a av:Style ] rdfs:label "nested sections are joined to their master" ;
    av:order 4 ;
    av:select [ a av:Selector ; av:element av:Details ] ;
    av:set [ av:connector av:line ] .

A heading line over a list, and its rows in a box

[ a av:Style ] rdfs:label "what we know, boxed" ;
    av:order 5 ;
    av:select [ a av:Selector ; av:element av:Source ; av:sourceId "props" ] ;
    av:set [ av:content [
        av:item [ a av:Text ; av:order 0 ; av:text "What we know" ] ;
        av:item [ a av:Layout, ex:Rows ; av:order 1 ;
            av:content [ av:item [ a av:Records ; av:order 0 ] ] ] ;
        av:item [ a av:Text ; av:order 2 ; av:text "That is all." ] ] ] .

[ a av:Style ] rdfs:label "the box is a quiet panel" ;
    av:order 6 ;
    av:select [ a av:Selector ; av:type ex:Rows ] ;
    av:set [ av:background av:surface ;
        av:paddingStart "tight" ; av:paddingEnd "tight" ] .

The list’s content is drawn once, however many rows there are: the first text over them, the last under them, and the rows alone inside the layout, which a second style finds by the class it was given. The page control stays at the foot of the list, outside the box. An empty list draws no box at all, only its texts. The rows are the same rows inside the box: the styles that find them by what they are (av:element, av:type, av:root) find them still. And it is a list of the perspective’s own that takes content: an invocation shows another perspective’s list, which is dressed by the styles written for that one.

Do without the row and the details

[ a av:Style ] rdfs:label "everything on the record itself" ;
    av:order 5 ;
    av:select [ a av:Selector ; av:element av:Record ] ;
    av:set [ av:content [
        av:item [ a av:Term ; av:order 0 ; av:role rdf:subject ] ;
        av:item [ a av:Source ; av:order 1 ; av:sourceId "about" ] ;
        av:item [ a av:Sources ; av:order 2 ] ] ] .

[ a av:Style ] rdfs:label "and nothing on the row" ;
    av:order 6 ;
    av:select [ a av:Selector ; av:element av:Main ] ;
    av:set [ av:content [ ] ] .

The first style puts the subject, then the about invocation, then whatever other lists there are, straight on the record. The second empties the row — without it the built-in line would stand as well, and the subject would show twice. The details, with nothing left to hold, stand nothing. This is not the recommended shape: the built-in styles that indent the details and caption the row assume the two folders, and a sheet that leaves them will be stating those things itself. But nothing stops it.

Read the built-in sheet. Everything you see in Arspec by default comes from one stylesheet of about twenty styles, and it is the shortest complete example of how a view is put together. Preferences → Profile → Your perspectives and styles opens the shipped vocabularies; the sheet itself is assets/view/1/default.ttl in the app's source.