This document contains guidelines to follow in order to maintain a consistent look and feel throughout the CollectionSpace user interface.
-
Panel labels should be title case.
-
The first information group panel on a record editor form should default to expanded. All others should default to collapsed.
Why: Records render faster and use less memory when panels are collapsed. The collapsed state of each panel is remembered for each user, so if a user wants a panel to be open, they can open it, and it will remain open on the next visit.
-
Field labels should be sentence case.
-
"Note" vs. "Notes": Singular.
// 🚫 BAD planningNote: { [config]: { messages: defineMessages({ name: { id: 'field.exhibitions_common.planningNote.name', defaultMessage: 'Planning notes', }, }), // ... }, },
// ✅ GOOD planningNote: { [config]: { messages: defineMessages({ name: { id: 'field.exhibitions_common.planningNote.name', defaultMessage: 'Planning note', }, }), // ... }, },
-
The default. Use in most cases.
-
Tab order: Down the left column to the end, then to the top of the right column.
// 🚫 BAD <Panel name="objectEntryInfo" collapsible> <Row> <Field name="entryNumber" /> <Field name="returnDate" /> </Row> <Row> <Field name="entryDate" /> <Field name="currentOwner" /> </Row> <Row> <Field name="entryReason" /> <InputTable name="depositor"> <Field name="depositor" /> <Field name="depositorsRequirements" /> </InputTable> </Row> <Cols> <Col> <Field name="entryMethods"> <Field name="entryMethod" /> </Field> </Col> <Col /> </Cols> </Panel>
// ✅ GOOD <Panel name="objectEntryInfo" collapsible> <Cols> <Col> <Field name="entryNumber" /> <Field name="entryDate" /> <Field name="entryReason" /> <Field name="entryMethods"> <Field name="entryMethod" /> </Field> </Col> <Col> <Field name="returnDate" /> <Field name="currentOwner" /> <InputTable name="depositor"> <Field name="depositor" /> <Field name="depositorsRequirements" /> </InputTable> </Col> </Cols> <Field name="entryNote" /> <Field name="packingNote" /> </Panel>
-
Use when higher field density is required.
-
Must have more than two fields per row.
-
Tab order: Left to right along a row, then to the beginning of the next row.
In CollectionSpace, repeating (multi-valued) fields are always modeled as a pair of nested fields: an outer (parent) field, and an inner (child) field. For example, in the record type configuration for intake
records, the multi-valued entry method field is defined as:
{
entryMethods: {
[config]: {
// ...
},
entryMethod: {
[config]: {
repeating: true,
// ...
},
},
},
};
Only the inner field (in this case, entryMethod
) has its repeating
property set to true
. Conceptually, this says that entry methods consists of one or more entry method. Because of this field structure, there are two possible places to define messages for a repeating field.
-
Do not define the
name
message on both the outer and inner fields.Why: Defining
name
messages on both the outer and inner fields causes two labels to be rendered on the record editor form, and is almost always redundant.// 🚫 BAD - name defined on both outer and inner fields { entryMethods: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.entryMethods.name', defaultMessage: 'Entry method', }, }), // ... }, entryMethod: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.entryMethod.name', defaultMessage: 'Entry method', }, }), repeating: true, // ... }, }, }, };
// ✅ GOOD - name defined only on inner field { entryMethods: { [config]: { // ... }, entryMethod: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.entryMethod.name', defaultMessage: 'Entry method', }, }), repeating: true, // ... }, }, }, };
-
Define the
name
message on the inner field.Why: In addition to the record editor form, the
name
message of the inner field is used in other places, such as the record sidebar and search form. Not defining it may result in the raw field name being displayed to the user in these places.// 🚫 BAD - name defined on outer field instead // of inner field { entryMethods: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.entryMethods.name', defaultMessage: 'Entry method', }, }), // ... }, entryMethod: { [config]: { repeating: true, // ... }, }, }, };
// ✅ GOOD - name defined on inner field { entryMethods: { [config]: { // ... }, entryMethod: { [config]: { repeating: true, messages: defineMessages({ name: { id: 'field.intakes_common.entryMethod.name', defaultMessage: 'Entry method', }, }), // ... }, }, }, };
A repeating group of fields is modeled like any repeating field, with an outer parent field containing a single inner repeating child. The only difference is that the inner field happens to be a group, meaning that it has child fields of its own (the group members). The above rules about defining messages continue to apply to the outer field and the inner (group) field.
A group field is typically configured with the view type CompoundInput
. The CompoundInput
component can render the member fields in a table, or it can render the member fields using a template you supply.
-
Use a table (set the
tabular
prop totrue
in the field's view configuration) when the group contains a small number of fields whose values are expected to be short. -
Define a
name
message for the group field.Why: This causes a label to be rendered for the entire group, making it easy for users to see how the how the member fields are related.
-
Avoid repeating the label (
name
message) of the group on the label (name
message) of any member field in the group. Usually you can either shorten the member field label to save space, or change it to something that gives the user more information.Why: Don't waste space on redundant/uninformative labels.
// 🚫 BAD - 'Current location' is repeated in the // group label and a member field label currentLocationGroupList: { [config]: { // ... }, currentLocationGroup: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationGroup.name', defaultMessage: 'Current location', }, }), repeating: true, // ... }, currentLocation: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocation.name', defaultMessage: 'Current location', }, }), // ... }, }, // ... }, },
// ✔️ BETTER - Shorten the member field label currentLocationGroupList: { [config]: { // ... }, currentLocationGroup: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationGroup.name', defaultMessage: 'Current location', }, }), repeating: true, // ... }, currentLocation: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocation.name', defaultMessage: 'Location', }, }), fullName: { id: 'field.intakes_common.currentLocation.fullName', defaultMessage: 'Current location', }, }, }, // ... }, },
// ✅ BEST - Change the member field label to // something that further describes the content // of the field currentLocationGroupList: { [config]: { // ... }, currentLocationGroup: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationGroup.name', defaultMessage: 'Current location', }, }), repeating: true, // ... }, currentLocation: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocation.name', defaultMessage: 'Name', }, }), fullName: { id: 'field.intakes_common.currentLocation.fullName', defaultMessage: 'Current location name', }, }, }, // ... }, },
-
Avoid repetitive member field labels. If some text appears in the label of every member field, it probably belongs in the label of the group.
Why: Don't waste space on redundant/uninformative labels.
// 🚫 BAD - 'Current location' is repeated on all // member labels currentLocationGroupList: { [config]: { // ... }, currentLocationGroup: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationGroup.name', defaultMessage: 'Current location', }, }), repeating: true, view: { type: CompoundInput, props: { tabular: true, }, }, }, currentLocation: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocation.name', defaultMessage: 'Current location', }, }), // ... }, }, currentLocationFitness: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationFitness.name', defaultMessage: 'Current location fitness', }, }), // ... }, }, currentLocationNote: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationNote.name', defaultMessage: 'Current location note', }, }), // ... }, }, }, },
// ✅ GOOD - Remove redundant text that's already // in the group label currentLocationGroupList: { [config]: { // ... }, currentLocationGroup: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationGroup.name', defaultMessage: 'Current location', }, }), repeating: true, view: { type: CompoundInput, props: { tabular: true, }, }, }, currentLocation: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocation.name', defaultMessage: 'Location', }, fullName: { id: 'field.intakes_common.currentLocation.fullName', defaultMessage: 'Current location', }, }), // ... }, }, currentLocationFitness: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationFitness.name', defaultMessage: 'Fitness', }, fullName: { id: 'field.intakes_common.currentLocationFitness.fullName', defaultMessage: 'Current location fitness', }, }), // ... }, }, currentLocationNote: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.currentLocationNote.name', defaultMessage: 'Note', }, fullName: { id: 'field.intakes_common.currentLocationNote.fullName', defaultMessage: 'Current location note', }, }), // ... }, }, }, },
-
Define a
fullName
message for each member field, unless thename
message is sufficient to identify the field when it is used out of context.
-
Use an input table (
InputTable
element in form template configuration) to visually join together a small number of related fields whose values are expected to be short. -
Always supply a
name
to the input table, and define a message for it.Why: This causes a label to be rendered for the entire table, making it easy for users to see how the fields are related.
// 🚫 BAD - no name specified on InputTable <InputTable> <Field name="depositor" /> <Field name="depositorsRequirements" /> </InputTable>
// ✅ GOOD - name specified on InputTable <InputTable name="depositor"> <Field name="depositor" /> <Field name="depositorsRequirements" /> </InputTable> // ✅ GOOD - message defined for the name { inputTable: defineMessages({ depositor: { id: 'inputTable.intake.depositor', defaultMessage: 'Depositor', }, }), };
-
Avoid repeating the label of the input table on the label (
name
message) of any field in the table. Usually you can either shorten the field label to save space, or change it to something that gives the user more information.Why: Don't waste space on redundant/uninformative labels.
-
Avoid repetitive field labels. If a word appears in every field label, it probably belongs in the label of the table.
Why: Don't waste space on redundant/uninformative labels.
-
Define a
fullName
message for each field in the table, unless thename
message is sufficient to identify the field when it is used out of context.// 🚫 BAD - fullName not defined on a field in // an input table, where the name is ambiguous { depositor: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.depositor.name', defaultMessage: 'Name', }, }), // ... }, };
// ✅ GOOD - fullName defined to fully // describe the field { depositor: { [config]: { messages: defineMessages({ name: { id: 'field.intakes_common.depositor.name', defaultMessage: 'Name', }, fullName: { id: 'field.intakes_common.depositor.fullName', defaultMessage: 'Depositor name', }, }), // ... }, };
-
If the fields in the table are enclosed in a repeating field group, define a
groupName
message for each field.Why: This ensures that each field can be unambiguously but concisely identified within a group search.
// 🚫 BAD - groupName not defined on a field in // an input table in a repeating group. The name // message is displayed, which is ambiguous. { personTermGroupList: { // ... personTermGroup: { // ... termSource: { [config]: { messages: defineMessages({ fullName: { id: 'field.persons_common.termSource.fullName', defaultMessage: 'Term source name', }, name: { id: 'field.persons_common.termSource.name', defaultMessage: 'Name', }, }), view: { type: AutocompleteInput, props: { source: 'citation/local,citation/shared,citation/worldcat', }, }, }, }, }, }, }
// ✅ GOOD - groupName defined to describe the // field within the context of the parent. { personTermGroupList: { // ... personTermGroup: { // ... termSource: { [config]: { messages: defineMessages({ fullName: { id: 'field.persons_common.termSource.fullName', defaultMessage: 'Term source name', }, groupName: { id: 'field.persons_common.termSource.groupName', defaultMessage: 'Source name', }, name: { id: 'field.persons_common.termSource.name', defaultMessage: 'Name', }, }), view: { type: AutocompleteInput, props: { source: 'citation/local,citation/shared,citation/worldcat', }, }, }, }, }, }, }
-
If a structured date is enclosed in a repeating field group, define a
groupName
message that is the same as thename
message.Why: On advanced search forms, the labels of fields inside of structured dates are algorithmically constructed. When searching within a group, the algorithm that constructs these labels will use the
fullName
message if agroupName
message is not present. This can be confusing for end users, because the structured date field itself may be displayed using thename
message.// 🚫 BAD - groupName not defined on a structured // date field in a repeating field group. Fields // inside the structured date are prefixed using // the fullName, but the structured date itself // is labeled using the name. { propActivityGroupList: { // ... propActivityGroup: { // ... activityDate: { [config]: { dataType: DATA_TYPE_STRUCTURED_DATE, messages: defineMessages({ fullName: { id: 'field.propagations_common.activityDate.fullName', defaultMessage: 'Activity date', }, name: { id: 'field.propagations_common.activityDate.name', defaultMessage: 'Date', }, }), view: { type: StructuredDateInput, }, }, }, }, }, }
// ✅ GOOD - groupName defined to be the same as // name { propActivityGroupList: { // ... propActivityGroup: { // ... activityDate: { [config]: { dataType: DATA_TYPE_STRUCTURED_DATE, messages: defineMessages({ fullName: { id: 'field.propagations_common.activityDate.fullName', defaultMessage: 'Activity date', }, groupName: { id: 'field.propagations_common.activityDate.groupName', defaultMessage: 'Date', }, name: { id: 'field.propagations_common.activityDate.name', defaultMessage: 'Date', }, }), view: { type: StructuredDateInput, }, }, }, }, }, }
- Option labels should be lowercase, except for proper nouns and abbreviations that are normally capitalized.