HTML and Visualforce¶
TODO(David)
Write this page. Stub created for structure only.
TODO
Visualforce page standards, LWC/Aura markup, escaping and XSS
Standards for markup, whether a developer writes it or a component generates it: accessibility, comments, Visualforce, CSS, SLDS 2, and XML.
Dynamically Generated HTML¶
TODO(David)
Find a new name for this heading. The current name is a placeholder.
The standards on this page are for the final HTML: the markup that reaches the browser, whether a developer wrote it or a Visualforce, Aura, or Lightning Web Component generated it.
The rules check what can be checked in the source, such as an alt attribute on a Visualforce image tag. The final HTML cannot be checked without additional tools, so some standards are left for those tools to enforce (How Accessibility Is Enforced).
Accessibility¶
Accessibility is a fundamental requirement: all markup conforms to WCAG 2.2 Level AA. The baseline, how it is enforced, and the United States and Australian laws that require it are set out on the Accessibility page.
Images¶
The standards for images, and for buttons and links that need a name, apply to every kind of markup, and they are on the General: Accessibility page.
Tables¶
A screen reader reads a data table one cell at a time, and can announce the headers of a cell only when the markup says which they are (WCAG 1.3.1, Level A).
- A data table says what it holds, in a
captionor in anaria-label,aria-labelledby, oraria-describedbyattribute with text (technique H39). Anapex:dataTableor anapex:pageBlockTablegenerates itscaptionfrom acaptionfacet (apex:dataTable). A table with none is reported at severity 4. Enforced byTableWithoutCaptionVf(Visualforce) andTableWithoutCaptionHtml(Lightning Web Components and Aura). - A data table marks its header cells with
th(technique H51). A table with nothis reported at severity 4. Enforced byTableWithoutHeadersVf(Visualforce) andTableWithoutHeadersHtml(Lightning Web Components and Aura). - A header cell has a
scope, or anidthat cells name in theirheadersattribute (technique H63, technique H43). Athwith neither is reported at severity 4. Athin a layout table is reported by the layout table rules, not by these. Enforced byTableHeaderWithoutScopeVf(Visualforce) andTableHeaderWithoutScopeHtml(Lightning Web Components and Aura). - A
headersattribute names only theidof athin the same table. A name that matches no header cell is reported at severity 4. Enforced byTableCellHeaderNotFoundVf(Visualforce) andTableCellHeaderNotFoundHtml(Lightning Web Components and Aura). - No table is used for layout. The HTML Standard does not allow a table as a layout aid (The table element): layout is done in CSS. A table with
role="presentation"orrole="none"is reported at severity 3. Enforced byLayoutTableVf(Visualforce) andLayoutTableHtml(Lightning Web Components and Aura). - A layout table carries no
caption,th,summary,headers, orscope. That markup tells assistive technology that the table holds data, which fails WCAG 1.3.1 (failure F46). A layout table with any of them blocks the pull request (severity 2). Enforced byLayoutTableWithSemanticMarkupVf(Visualforce) andLayoutTableWithSemanticMarkupHtml(Lightning Web Components and Aura).
Form Groups, Frames, and Lists¶
- A
fieldsethas alegendthat names the group of controls inside it (technique H71). Afieldsetwithout one is reported at severity 4. Enforced byFieldsetWithoutLegendVf(Visualforce) andFieldsetWithoutLegendHtml(Lightning Web Components and Aura). - An
iframe, aframe, or anapex:iframehas atitlethat says what the frame holds (technique H64). A frame without one is reported at severity 4. Enforced byFrameWithoutTitleVf(Visualforce) andFrameWithoutTitleHtml(Lightning Web Components and Aura). - A list item is inside a list: an
liin aul,ol, ormenu, and adtorddin adl(technique H48). A screen reader announces a list and its length only when the markup is a list. An item outside a list is reported at severity 4. Enforced byListItemOutsideListVf(Visualforce) andListItemOutsideListHtml(Lightning Web Components and Aura).
A tag that only repeats or groups its content, such as apex:repeat, aura:iteration, or a template tag with for:each, may sit between a list and its items. A div that groups a dt and its dd may sit in a dl. An li at the root of a child component is not reported, because its list is in the parent. The root template of a Lightning web component, an aura:component, and an apex:component are treated alike. In a Visualforce page or an Aura application, an li with no list above it is reported.
Media¶
- A
videohas atrackof captions or subtitles (WCAG 1.2.2, Level A; technique H95). Atrackwith nokindcounts, because subtitles are the default (The track element). Avideowithout one is reported at severity 3. Enforced byVideoWithoutCaptionsVf(Visualforce) andVideoWithoutCaptionsHtml(Lightning Web Components and Aura). - An
objectelement holds fallback content, for a browser or a reader that cannot show the object (technique H53). Anobjectwith none is reported at severity 4. Enforced byObjectWithoutAlternativeVf(Visualforce) andObjectWithoutAlternativeHtml(Lightning Web Components and Aura). - No image is a server-side image map. The
ismapattribute sends the server the coordinates of a click, so the map cannot be worked from the keyboard (WCAG 2.1.1, Level A). Anismapattribute blocks the pull request (severity 2), whatever its value, as an image-map link with no name does: on animg, the attribute being there turns the map on (Boolean attributes). Onlyismap="false"on anapex:imageis not reported. Enforced byServerSideImageMapVf(Visualforce) andServerSideImageMapHtml(Lightning Web Components and Aura).
Keyboard¶
- Every function that a mouse reaches, a keyboard reaches too (WCAG 2.1.1, Level A). An
onmouseoverhas anonfocus, and anonmouseouthas anonblur(technique SCR2). Anonclickhas anonkeydown,onkeyup, oronkeypress(technique SCR20). A mouse handler with no keyboard equivalent is reported at severity 3. Enforced byMouseEventWithoutKeyboardEventVf(Visualforce) andMouseEventWithoutKeyboardEventHtml(Lightning Web Components and Aura). - A browser fires
clickfrom the keyboard on a link, an image-maparea, a button, a form control, and asummary, so theironclickneeds no key handler. The same is true of a Visualforce component that renders one of them, such asapex:commandButton,apex:commandLink,apex:outputLink, or an input component, and of a Lightning component, which handles its own keyboard behaviour. Anapex:outputPanelrenders adivor aspan, so itsonclickneeds a key handler.
Landmarks¶
- When a page has two or more landmarks with the same role, each has a label that is different from the others, in an
aria-labeloraria-labelledbyattribute (technique ARIA11, for WCAG 1.3.1 and WCAG 2.4.1, Level A). A screen reader lists the landmarks of a page, and a user moves from one to the next. With two navigation landmarks and no labels, the user cannot tell the main menu from the footer links. A landmark with no label, or with the label of another landmark of its role, is reported at severity 4. Enforced byRepeatedLandmarkWithoutUniqueLabelVf(Visualforce) andRepeatedLandmarkWithoutUniqueLabelHtml(Lightning Web Components and Aura). - The landmarks are
nav(navigation),aside(complementary),main,search, aformwith a label, and any element whoserolenames a landmark role, such asrole="navigation"orrole="banner". Aformwith no label is not a landmark, so anapex:formis not counted. Aheader,footer, orsectionis counted only by itsrole, because it is a landmark only in some places. - An empty label counts as no label. Two labels are the same when their text is the same, without regard to case and spaces. A merge expression or a bound property counts as a unique label, because its value is not known before the page runs.
- The rules compare the landmarks of one file. When the landmarks of two components show on one page, reviewers check that their labels are different.
TODO
Labels and ARIA, SLDS semantics
Source (Well-Architected, Compliant / Accessibility; see standards-sources memory)
Colour and contrast (Well-Architected cites WCAG 2.0, but the site's normative baseline is WCAG 2.2 AA per David's 2026-07-12 ruling in standards-sources Standing decisions), never colour alone to convey meaning, visible keyboard focus at all times, form usability (visible labels, helpful errors, progress indication), test with multiple input devices, Salesforce ACRs as the baseline conformance documents
Sources:
- apex:dataTable - Visualforce Developer Guide
- ARIA11: Using ARIA landmarks to identify regions of a page - W3C
- Boolean attributes - HTML Standard
- F46: Failure of Success Criterion 1.3.1 due to using th elements, caption elements, or… - W3C
- H39: Using caption elements to associate data table captions with data tables - W3C
- H43: Using id and headers attributes to associate data cells with header cells in data tables - W3C
- H48: Using ol, ul and dl for lists or groups of links - W3C
- H51: Using table markup to present tabular information - W3C
- H53: Using the body of the object element - W3C
- H63: Using the scope attribute to associate header cells with data cells in data tables - W3C
- H64: Using the title attribute of the iframe element - W3C
- H71: Providing a description for groups of form controls using fieldset and legend elements - W3C
- H95: Using the track element to provide captions - W3C
- HTML Standard: Tables
- SCR20: Using both keyboard and other device-specific functions - W3C
- SCR2: Using redundant keyboard and mouse event handlers - W3C
- The track element - HTML Standard
- Understanding Success Criterion 1.2.2: Captions (Prerecorded) - W3C
- Understanding Success Criterion 1.3.1: Info and Relationships - W3C
- Understanding Success Criterion 2.1.1: Keyboard - W3C
- Understanding Success Criterion 2.4.1: Bypass Blocks - W3C
- Web Content Accessibility Guidelines (WCAG) 2.2
Credentials in Markup¶
A credential in the markup of a component is in the repository, as one in a class is (Security).
- No credential is the default value of an attribute. An
aura:attributein an Aura component, or anapex:attributein a Visualforce component, whose name says that it holds a credential, such asapiKeyorpassword, and that has a default value blocks the pull request (severity 2). Enforced byAvoidHardCodedCredentialsInAura(Aura) andAvoidHardcodedSecretsInVFAttrs(Visualforce).
Both rules read the name and not the value. They also report a harmless default under such a name, and a custom component (c:) that is given an attribute with such a name, whatever its value. The owning team accepts a false alarm of that kind (Fixing a Blocking Violation).
Comments¶
The General: Comments standards apply to markup as they do to code. These are particular to markup.
TODO,TO-DO,FIXME,FIX-ME, orHACKin a markup comment (<!-- ... -->), as the first word of one of its lines or with a colon after it, in a Visualforce page or component, a Lightning Web Component template, or an Aura component, is reported at severity 5. Enforced byNoUnresolvedTodosMarkup.- A Visualforce page or component starts with a comment that says what it is for, before its first tag; blank lines may come first. A page or component that does not is reported at severity 4. The rule checks only that a comment comes first, and reviewers check what it says. Enforced by
VisualforceHeaderComment. - The Salesforce Documenter (SFDoc) extension writes its default author,
ChangeMeIn@UserSettingsUnder.SFDoc, into the header of a Visualforce page or component, a Lightning Web Component template, and an Aura component. That string in any of those files blocks the pull request (severity 2). Enforced byNoSfdocDefaultAuthor.
Lightning Frameworks HTML¶
Standards that apply to the HTML of Lightning Web Component templates and Aura markup. The standards that hold for all markup, Visualforce included, are in sections of their own on this page.
Readability¶
Code Style¶
TODO
Stub. Standards that make HTML easier to read and that may not change what it does: stylistic choices (Content item 142).
Formatting¶
TODO
Stub. The Prettier and purely cosmetic standards of HTML, such as tabs, bracket spacing, and the space before a self-closing slash (Content item 142).
Whitespace Sensitivity¶
TODO
Stub. Where white space between two tags changes what a user sees in a Lightning Web Component template or Aura markup, and the Prettier setting that these standards choose for it: htmlWhitespaceSensitivity (Prettier, What an Organisation Customises, "White space in Visualforce markup") (Content item 142).
Required Formatting¶
TODO
Stub. Formatting that the platform requires, as against formatting that these standards choose (Content item 142).
Error Prevention¶
TODO
Stub. Code choices in HTML that work today and could cause a problem later (Content item 142).
- No attribute in a Lightning Web Component template, in Aura markup, or in a plain HTML file holds a
javascript:URL, as in Visualforce (Resources and Links). In a component, the Content Security Policy of the platform blocks an inline script, so such a link or handler does nothing (Scripts and Resources). The event is handled in the component's JavaScript, and a link has a real address. Ajavascript:URL blocks the pull request (severity 2), andjavascript:void(0)is allowed through theallowedPatternproperty. Enforced byAvoidJavaScriptUrlHtmlLwcAura. - A Lightning Web Component template or Aura markup does not hold the absolute URL of a Salesforce org, as a Visualforce page does not (Resources and Links). A link is relative, or comes from a bound property that the component's JavaScript builds. The same
allowedSubdomainsproperty names the Salesforce websites that may be written out. A URL of a Salesforce org is reported at severity 3, in attribute values and in text. Enforced byAvoidAbsoluteSalesforceUrlHtml.
Visualforce¶
Standards that apply to Visualforce pages and components.
- Lightning Web Components is the default framework for custom UI, and a Visualforce page or component is built only where a Lightning web component cannot do the same thing. Salesforce recommends Lightning Web Components over Visualforce for new development (Why Should I Use Lightning Web Components instead of Visualforce?, Introducing Visualforce).
- A Lightning web component is more accessible by default than a Visualforce page. Salesforce gives support for the latest WCAG as a reason to rebuild a Visualforce page as a Lightning web component (Why Should I Use Lightning Web Components instead of Visualforce?), and the Lightning base components have accessibility built in (Accessibility Attributes).
- The Centre of Excellence (CoE) checks the choice between Lightning Web Components and Visualforce at design time, when it approves the design document.
Standard Components¶
A Visualforce page is built from the standard Visualforce components, such as apex:pageBlockSection, wherever one does the job (Standard Visualforce Component Reference). This is Built-In First applied to Visualforce.
- A standard component is used wherever there is one for the purpose. Plain HTML is mixed in as needed, where no component fits.
- Fields are laid out with the standard layout components wherever possible:
apex:pageBlock,apex:pageBlockSection,apex:inputField, andapex:outputField. They carry Salesforce's own styling, so a page built from them takes on new Salesforce styles with no change to its markup. WithlightningStylesheets="true"onapex:page, for example, they are styled to match Lightning Experience (Style Existing Visualforce Pages with Lightning Experience Stylesheets). - JavaScript is used sparingly, because many components have it built in.
apex:actionSupport, for example, refreshes part of a page when an event occurs, with no script written for it (apex:actionSupport). - A page or component has one
apex:form, which Salesforce calls a best practice (apex:form). A part of the page that posts on its own uses anapex:actionRegioninside the one form. A second form is reported at severity 3. Enforced byAvoidMultipleFormsVf. - A page does not frame another page with
apex:iframeoriframe. It includes a component, or links to the page. Visualforce pages and custom iframes are not supported in Lightning Experience on iPad Safari (Developing Salesforce Apps with Visualforce). A page that embeds a third-party site has no other way, and its owning team accepts the report. A frame is reported at severity 4. Enforced byAvoidIframeVf.
No rule checks these standards. Reviewers do.
External Scripts and Stylesheets¶
A Visualforce page loads its JavaScript and its CSS from static resources, never from another site. A script fetched from another host runs with the page's access to the user's session and data, and either kind of file can change without a review or a deployment.
- A script comes from a static resource, in a
scripttag and inapex:includeScriptalike. One loaded from anywhere else blocks the pull request (severity 2). Enforced byLoadJavaScriptHtmlScriptandLoadJavaScriptIncludeScript. - A stylesheet comes from a static resource, in a
linktag and inapex:stylesheetalike. One loaded from anywhere else blocks the pull request (severity 2). Enforced byLoadCSSLinkHrefandLoadCSSApexStylesheet. - Nothing loads from
polyfill.io. After the domain changed hands in 2024, the script it served was altered to send visitors to malicious sites (Sansec). A reference to it in a Visualforce page or component, in Lightning Web Component or Aura markup, or in a metadata file such as a CSP Trusted Site, blocks the pull request (severity 2). Enforced byNoPolyfillIo.
A reference through $Resource, with or without URLFOR, is the best practice. A script written in the page itself is also allowed.
- A Visualforce page or component should not use Lightning Components for Visualforce:
apex:includeLightning,lightning.out.js, or$Lightning.use. It is built on Lightning Out (beta), which requires Lightning Locker (Use Components in Visualforce Pages, Lightning Web Security). A page or component that uses it is reported and does not block (severity 3), and an organisation may allow it with a suppression and a documented reason. Enforced byNoLightningOutInVisualforce.
Resources and Links¶
- A static resource is referenced by name through
$Resource, withURLFORfor a file inside an archive, and not by a/resource/path (Using Static Resources). A hard-coded/resource/path is reported at severity 3. Enforced byAvoidHardcodedResourcePathVf. - A link to another Visualforce page is written with
$Page, and not as an/apex/path ($Page). A hard-coded/apex/link is reported at severity 3. Enforced byAvoidHardcodedPageLinkVf. - A link does not point to a hard-coded record Id, such as
/001…. A record Id belongs to one org, so the link is wrong in every sandbox and every other org, the same as in Apex. A link to a record is built withURLFORand$Action, as the Visualforce Developer Guide advises (Understanding the Salesforce Mobile App Container), or from a field of the record. A link to a hard-coded record Id blocks the pull request (severity 1). A link built from an expression, such as/{!acc.Id}, is not reported. Enforced byAvoidHardcodedIdVf. - A page does not hold the absolute URL of a Salesforce org, the same as Apex. Every sandbox and every production org has a host name of its own, so the URL of one org is wrong in every other org, and it breaks when the org's My Domain name changes. A link within the org is relative, or built from
$Site,URLFOR, or$Page. A Salesforce website that is the same for every org, such as Salesforce Help, may be written out, and the rule'sallowedSubdomainsproperty names those websites. A URL of a Salesforce org is reported at severity 3, in attribute values, page text, script text, and strings inside an expression. Enforced byAvoidAbsoluteSalesforceUrlVf. - No attribute holds a
javascript:URL. MDN warns that ajavascript:URL may lead to the execution of arbitrary code, aseval()does (javascript: URLs). The event is handled in a script, and a link has a real address. Ajavascript:URL blocks the pull request (severity 2).javascript:void(0), a link that does nothing, is allowed: the rule'sallowedPatternproperty holds the values that may be written, and each organisation sets its own. Enforced byAvoidJavaScriptUrlVf. The same standard applies in Lightning markup (Error Prevention), and in JavaScript files at the same severity (JavaScript).
Console Statements¶
A script block in a Visualforce page or component is JavaScript, and it follows the JavaScript standards. A console statement there can be read by anyone who opens the browser's developer tools, as one in a component can (Console and Debugger Statements).
- No console statement in a Visualforce page or component. One blocks the pull request (severity 2). Enforced by
NoConsoleInVisualforce.
Script Blocks¶
- A script block does not hold the text of another script tag. The HTML parser gives
<scripta special meaning inside a script block, so the block can end somewhere other than where its author meant (Restrictions for contents of script elements). A script that has to write a script tag escapes it as\x3Cscript, or the page loads the other script withapex:includeScript. A script block that holds a script tag is reported at severity 3. Enforced byNestedScriptElement.
A hard-coded path is not adjusted for the path prefix of an Experience Cloud site, and the two global variables are.
Naming¶
The naming standards for Visualforce, for pages, components, and controllers, are on the General: Naming page, with the naming standards of the other languages.
Readability¶
Code Style¶
TODO
Stub. Standards that make Visualforce easier to read and that may not change what it does: stylistic choices (Content item 142).
Formatting¶
- Formatting is applied by Prettier, from the repository's Prettier configuration file, on save and again when a commit is made (Section 4). It is not a matter for review. A page or component that is not formatted blocks the pull request (Appendix A.2.1).
- A Visualforce page or component is formatted with Prettier's
htmlparser, as Salesforce's own Prettier configuration sets it (Prettier Code Formatter). - Markup is indented with tabs, and a line is at most 180 characters wide (Version Control: Prettier).
- The
>that closes a start tag stays on the line of its last attribute (bracketSameLineistrue), and attributes share a line until the line is full (singleAttributePerLineisfalse). - A self-closing tag has a space before its slash:
<apex:outputText value="{!name}" />. Prettier always writes it so in markup. - Attribute values are in double quotes (
singleQuoteisfalse). - A page or component retrieved from an org is formatted again before it is committed. When it saves a page, Salesforce moves the first attribute of each
apex:tag back to the line of the tag name, joins an end tag that Prettier split over two lines, and removes the final newline. It keeps tabs, spaces, and line endings as they were. - Visualforce is formatted with Prettier only, and not with the Visualforce formatter of the Salesforce Extensions for Visual Studio Code, which changes white space and how a page renders.
Whitespace Sensitivity¶
Visualforce removes the white space just before an apex: start tag or end tag when it renders a page. So Read the <apex:outputLink value="{!url}">guide</apex:outputLink> shows "Read theguide", and two components with a space between them show with none. White space after an apex: tag, and white space next to a plain HTML tag, is kept.
htmlWhitespaceSensitivityis"css". Prettier then breaks a line only where white space already is, or inside a tag, and the page that a user sees does not change. With"ignore", Prettier puts line breaks between inline elements, and the page shows a space where there was none (HTML Whitespace Sensitivity).- A space that must show before an
apex:tag is written as the merge expression{!' '}:Read the{!' '}<apex:outputLink value="{!url}">guide</apex:outputLink>. Visualforce writes it as a space, and Prettier never breaks a line inside it. A space at the end of a plain HTML element's text (<span>Read the </span>), or at the end of thevalueof the component before the tag, is also kept. The character reference is removed like a typed space, and shows but stops the line from wrapping there.
No rule checks this. Reviewers do.
Required Formatting¶
TODO
Stub. Formatting that the platform requires, as against formatting that these standards choose (Content item 142).
Error Prevention¶
TODO
Stub. Code choices in Visualforce that work today and could cause a problem later (Content item 142).
- A merge expression in a JavaScript string has a string of its own. Other text is joined to it with
+.
The problem: a string with a merge expression and escaped quotes, such as '{!JSENCODE("Tom")} isn\'t the owner\'s or the team\'s'. Prettier changes the string to double quotes, because that needs fewer backslashes. It also puts a backslash before each double quote in the merge expression. Salesforce cannot read the merge expression, and it does not save the page.
The prevention: '{!JSENCODE("Tom")}' + " isn't the owner's or the team's". Prettier does not change this.
No rule checks this. Reviewers do.
Security¶
A Visualforce page runs in the user's Salesforce session, with the user's access to data. A flaw in the page gives that access to whoever can exploit it. Two attacks matter most for a Visualforce page: cross-site scripting and cross-site request forgery. Salesforce defends against both by default, and a page that goes around a default loses that defence. The three standards below keep the defaults in place.
Cross-Site Scripting¶
In a cross-site scripting (XSS) attack, text that an attacker controls reaches a page and runs there as script. The script runs as the user who opened the page: it can read every record that the user can see, change records, and send the user's session to the attacker, who can then act as the user (Cross Site Scripting (XSS)). The attacker's text can come from a URL parameter, from a field that another user filled in, or from any record the page shows. One flaw on one page is enough: when an administrator opens the page, the script has the administrator's access.
The usual route into a Visualforce page is a merge expression, such as {!accountName}, that is written out unescaped. Visualforce encodes a merge expression for HTML wherever it writes one into the text of the page, so that <script> arrives as text and not as a tag. That encoding is right for the text of the page and wrong for other places. Inside a script, an event handler, a link, or a style, a value needs the encoding of that place, and escape="false" turns the encoding off altogether (Unescaped Output and Formulas in Visualforce Pages). A value that is safe today, such as a number from the controller, is encoded too: when the controller later reads it from a URL parameter, the page is safe without a change.
- A merge expression is always encoded for the place it is written. Text with
escape="false"is encoded withHTMLENCODE; a merge expression in a script or in an event handler attribute is encoded withJSENCODE(JSINHTMLENCODEalso passes); and a merge expression that starts a link is encoded withURLENCODE. A merge expression without the encoding its place needs blocks the pull request (severity 2). Enforced byVfUnescapeEl. - A merge expression in a
styleelement is encoded: withURLENCODEorJSINHTMLENCODEinside aurl(), and with an encoding function such asHTMLENCODEanywhere else in the style. Salesforce advises against any merge expression in astyleelement whose value the controller has not checked against an allowlist (Secure Coding Cross Site Scripting). An unencoded merge expression in astyleelement blocks the pull request (severity 2). Enforced byVfHtmlStyleTagXss.
Cross-Site Request Forgery¶
In a cross-site request forgery (CSRF) attack, a page on another site makes the user's browser send a request to Salesforce. The browser sends the user's session with the request, so Salesforce runs it as the user, who never meant to send it (Cross-Site Request Forgery (CSRF)). A link in an email or an image on a forum page is enough to send it.
Visualforce puts a random token in every form that it renders, and checks the token when the form is submitted, so a request from another site cannot submit a Visualforce form. The action attribute of apex:page is different: its method runs when the page loads, before any form, and no token is checked. An attacker who sends the user to the page, with the URL parameters that the attacker chooses, runs that method as the user. If the method deletes or changes a record, the attacker decides which one.
- A page changes no data when it loads. The
actionattribute ofapex:pagedoes not call a method that inserts, updates, or deletes a record, or that sends anything out of the org. A change waits until the user submits a form, or confirms on a page of its own. Anactionattribute onapex:pageblocks the pull request (severity 2). The rule cannot see what the method does, so it reports everyactionattribute; the owning team accepts one whose method only reads, such as a redirect (Fixing a Blocking Violation). Enforced byVfCsrf. The constructor of the page's controller runs when the page loads too, and the Apex standards cover it (Apex: Cross-Site Request Forgery).
Session Ids¶
A session Id is a credential: whoever holds it can act as the user until the session ends (Apex: Session Ids).
- A Visualforce page does not read the session Id, in a script or in a merge expression. A call to Salesforce from a page goes through the page's controller, a Named Credential, or Visualforce remoting.
GETSESSIONID()and$Api.Session_IDeach block the pull request (severity 2) until they are removed or a reviewer accepts them. Enforced byAvoidUnauthorizedGetSessionIdInVisualforceandAvoidUnauthorizedApiSessionIdInVisualforce.
Sources:
- $Page - Visualforce Developer Guide
- Accessibility Attributes - Lightning Web Components Developer Guide
- apex:actionSupport - Visualforce Developer Guide
- apex:form - Visualforce Developer Guide
- Cross Site Scripting (XSS) - Visualforce Developer Guide
- Cross-Site Request Forgery (CSRF) - Visualforce Developer Guide
- Developing Salesforce Apps with Visualforce - Visualforce Developer Guide
- HTML Standard: Scripting
- Introducing Visualforce - Visualforce Developer Guide
- javascript: URLs - URIs - MDN
- Options · Prettier
- Polyfill supply chain attack hits 100K+ sites - Sansec
- Prettier Code Formatter - Salesforce Extensions for Visual Studio Code
- Secure Coding Cross Site Scripting - Secure Coding Guide
- Standard Visualforce Component Reference - Visualforce Developer Guide
- Style Existing Visualforce Pages with Lightning Experience Stylesheets - Visualforce Developer Guide
- Understanding the Salesforce Mobile App Container - Visualforce Developer Guide
- Unescaped Output and Formulas in Visualforce Pages - Visualforce Developer Guide
- Use Components in Visualforce Pages - Lightning Web Components Developer Guide
- Using Static Resources - Visualforce Developer Guide
- Why Should I Use Lightning Web Components instead of Visualforce? - Visualforce Developer Guide
CSS¶
Standards for the stylesheets of Lightning Web Components, Aura components, and Visualforce pages.
- Styling should live in a stylesheet, not in the page. In Visualforce, a
styleelement and astyle,labelStyle, orhtml-styleattribute are inline styles; in a Lightning template, astyleattribute is. An inline style is reported and does not block (severity 3). Enforced byAvoidInlineStylesin Lightning templates and byAvoidInlineStylesInVisualforcein Visualforce pages and components. - CSS is formatted by Prettier, like the code beside it (Formatting).
- A focus outline is never removed without an equally visible replacement.
outline: noneleaves a keyboard user with no sign of where they are, and fails WCAG 2.4.7, a Level AA criterion. No rule checks this today. It is a blocking standard: a reviewer rejects a pull request that breaks it. - Text contrasts with its background by at least 4.5 to 1, or 3 to 1 for large text (WCAG 1.4.3, Level AA), and colour is never the only way that information is shown (WCAG 1.4.1, Level A). Contrast cannot be read from a stylesheet alone, so it is checked in the rendered page by the accessibility tests (How Accessibility Is Enforced).
Valid CSS¶
A browser drops a declaration it does not understand, without an error, so a mistake in a stylesheet fails silently.
- Every property, unit, colour, selector, pseudo-class, pseudo-element, media feature, and at-rule is one that CSS defines.
- A
linear-gradientdirection is a valid one, and the operands ofcalchave a space on each side of+and-. - A string does not run across a line break, and a comment is written
/* */: CSS has no//comment.
No Dead or Duplicated CSS¶
- No selector, property,
@import, or font name is written twice, and no shorthand property overrides a longhand one written before it. - No stylesheet is empty, and none carries an empty comment or a stray semicolon.
!importantis not used inside@keyframes, where browsers ignore the declaration.- A
font-familylist ends with a generic family, such assans-serif, so that the text always has a fallback.
No rule in this reference checks the standards under these two headings. A reviewer does.
Readability¶
Code Style¶
TODO
Stub. Standards that make CSS easier to read and that may not change what it does: stylistic choices (Content item 142).
Formatting¶
TODO
Stub. The Prettier and purely cosmetic standards of CSS, such as tabs, bracket spacing, and the space before a self-closing slash (Content item 142).
Required Formatting¶
TODO
Stub. Formatting that the platform requires, as against formatting that these standards choose. CSS is not sensitive to white space, with one exception to check: + and - inside calc() need white space on both sides (Content item 142).
Error Prevention¶
TODO
Stub. Code choices in CSS that work today and could cause a problem later (Content item 142).
Sources:
SLDS 2¶
Standards for SLDS 2 in Lightning Web Components and Aura components: the styling hooks of their stylesheets and the SLDS classes of their markup.
About SLDS 2¶
The Salesforce Lightning Design System (SLDS) is Salesforce's design system for Lightning Experience: its CSS framework, its component blueprints, and its guidelines. Salesforce introduced Salesforce Lightning Design System 2 (SLDS 2) in Spring '25, and now calls the original version SLDS 1 (Compare Salesforce Lightning Design System Versions).
SLDS 2 separates the structure of a component from its visual design. The visual design comes from styling hooks: CSS custom properties, such as --slds-g-color-border-1, whose values a theme sets. An org uses SLDS 2 when it chooses the Salesforce Cosmos theme; the default theme of SLDS 1 is Lightning Blue. Both versions use the same component blueprints, the markup and classes of each SLDS component.
A component that takes its colours, spacing, and type sizes from styling hooks follows the theme of the org, in SLDS 1 and in SLDS 2. A component that hard-codes them, or that uses the design tokens of SLDS 1, does not: SLDS 2 themes do not support design tokens. The standards in this section keep a component ready for SLDS 2. Salesforce does not yet support component styling hooks, whose names start with --slds-c-, in SLDS 2, and recommends that components that use them stay on SLDS 1 for now.
Every rule in this section is a rule of Salesforce's SLDS linter, @salesforce-ux/eslint-plugin-slds. Code Analyzer bundles that package, so there is nothing to install: the rules run wherever Code Analyzer runs. They check stylesheets (.css and .scss) and the markup of Lightning Web Components and Aura components (.html and .cmp). Code Analyzer does not give them Visualforce pages.
Styling Hooks¶
- Colours, spacing, and type sizes come from SLDS 2 styling hooks, not from hard-coded values. A hook follows the org's theme and carries Salesforce's own accessible colour choices. A hard-coded value is reported at severity 4. Enforced by
@salesforce-ux/slds/no-hardcoded-values-slds2. - A styling hook has its current name. A legacy
--sds-hook is replaced by the--slds-hook of the same name:var(--slds-g-color-border-1, #e5e5e5), notvar(--sds-g-color-border-1, #e5e5e5). A component styling hook, which only SLDS 1 supports for now (About SLDS 2), follows the current naming convention too:--slds-c-button-spacing-block-start, not--slds-c-button-spacing-blockstart. A legacy--sds-hook is reported at severity 4, and so is a component hook with an old name. Enforced by@salesforce-ux/slds/enforce-sds-to-slds-hooksand@salesforce-ux/slds/enforce-component-hook-naming-convention. - A deprecated
--lwc-design token is replaced by the SLDS 2 styling hook that matches it, and the token stays as the fallback value:var(--slds-g-color-neutral-base-90, var(--lwc-colorBorder)), notvar(--lwc-colorBorder). The order is never reversed: an--slds-hook is not the fallback value of an--lwc-token. A deprecated token is reported at severity 3, and an--slds-hook that is the fallback value of a token at severity 4. Enforced by@salesforce-ux/slds/lwc-token-to-slds-hookand@salesforce-ux/slds/no-sldshook-fallback-for-lwctoken. - No styling hook is used that SLDS 2 does not have. A hook that SLDS 2 removed, such as
--slds-g-color-border-base-2, has no direct replacement: it is removed, or replaced with a hook that has a similar effect. A hook that SLDS 2 does not have is reported at severity 3. Enforced by@salesforce-ux/slds/no-unsupported-hooks-slds2.
Safe Use of Styling Hooks¶
- Every SLDS styling hook in a
var()has a fallback value, such asvar(--slds-g-color-border-1, #e5e5e5). Styling hooks are not available in every Salesforce environment, and the fallback value is what the component shows where they are not. The fallback value may be a static value. A hook with no fallback value is reported at severity 3. Enforced by@salesforce-ux/slds/no-slds-var-without-fallback. - A custom styling hook has a name in the component's own namespace, such as
--myapp-gap, never in the--slds-or--sds-namespace, which SLDS keeps for its own hooks. A name in those namespaces that SLDS 2 does not define is reported at severity 4, whether it is set or used invar(), so a misspelt or removed SLDS hook is reported too. Enforced by@salesforce-ux/slds/no-slds-namespace-for-custom-hooks. -
No stylesheet sets or uses a private SLDS styling hook, one whose name starts with
--_slds-or--slds-s-. Salesforce keeps those hooks for its own use. A stylesheet that sets a--_slds-hook is reported at severity 2. Enforced by@salesforce-ux/slds/no-slds-private-var. Salesforce's description of the rule names both prefixes, but a test of version 1.2.1 of the linter, the version that Code Analyzer 5.16.0 bundles, showed that the rule reports only a--_slds-hook that a stylesheet sets.How the private hook rule was tested, and the results
Code Analyzer 5.16.0 scanned the stylesheet of a Lightning web component with the rules of the SLDS linter. The stylesheet held one case of each kind.
Case Example Reported A --_slds-hook that the stylesheet sets--_slds-c-button-color: #0176d3;Yes A --_slds-hook used invar()color: var(--_slds-c-button-color, #0176d3);No A --slds-s-hook that the stylesheet sets--slds-s-button-color: #0176d3;No A --slds-s-hook used invar()color: var(--slds-s-button-color, #0176d3);No
SLDS Classes¶
- An SLDS class is never overridden in a stylesheet. A component that needs a different look gets a class of its own, in its own namespace, such as
myapp-button, beside the SLDS class. An override of an SLDS class is reported at severity 4. Enforced by@salesforce-ux/slds/no-slds-class-overrides. - No class is used that SLDS 2 does not have, such as
slds-action-overflow_touch. It is replaced by a class that SLDS 2 supports. A class that SLDS 2 does not have is reported at severity 3, in markup and in stylesheets. Enforced by@salesforce-ux/slds/no-deprecated-classes-slds2. - An SLDS class name follows the BEM (block, element, modifier) convention of SLDS 2. A block is a component, such as
slds-buttonorslds-modal. An element is a part of a block, after two underscores, such asslds-modal__containerorslds-modal__close. A modifier is a variant of a block, after a single underscore, such asslds-button_brandorslds-text-heading_small. The double dash that SLDS once used for a modifier is retired:slds-button--brandis nowslds-button_brand,slds-text-heading--smallis nowslds-text-heading_small, andslds-tabs--default__item, an element of a modified block, is nowslds-tabs_default__item. A class name with the double dash is reported at severity 4. Enforced by@salesforce-ux/slds/enforce-bem-usage.
Modal Close Button¶
- The close button of a modal, the element with the class
slds-modal__close, follows the SLDS modal blueprint. It does not have the classslds-button_icon-inverse. Alightning-button-iconclose button has the classesslds-buttonandslds-button_icon, and the variant of its icon isbare, notbare-inverse; so is the variant of alightning-iconinside a close<button>. A close button that does not follow the blueprint is reported at severity 3. Enforced by@salesforce-ux/slds/modal-close-button-issue.
Suppression Comments¶
- No finding of the SLDS linter is hidden with one of its suppression comments,
@sldsValidatorIgnore,@sldsValidatorIgnoreNextLine, or@sldsValidatorAllow, without an approving review from the owning team, as for every other suppression (Comments). A suppression comment of the SLDS linter blocks the pull request (severity 2) until the finding is fixed or the owning team approves the suppression. Enforced by@salesforce-ux/slds/reduce-annotations.
Sources:
XML¶
Standards for the layout of XML files, the metadata files of Salesforce included. The Metadata Standards pages hold what the metadata says.
Formatting¶
Prettier formats XML with @prettier/plugin-xml, and the configuration of these standards sets each of the cosmetic standards below (Prettier).
- White space inside an element is preserved. Prettier indents the elements and leaves the text inside an element, such as a description or a formula, exactly as it is, so that it never changes the text of an element (
xmlWhitespaceSensitivityis"preserve"). - Most XML files are indented with tabs, as code is. A Salesforce metadata file is the exception: it is indented with spaces, not tabs. A
-meta.xmlfile and a manifest are indented with 4 spaces, as Salesforce writes them, so that a formatted file and a retrieved file differ in as few lines as possible. - An empty element in a metadata file has no space before its closing slash, as Salesforce writes it (
xmlSelfClosingSpaceisfalse). Any other XML file has the space.
Duplicated Markup¶
- No block of markup is copied from one page or component to another. A duplicated block is a component that both use, so that a change is made once. A block of HTML or Visualforce markup that appears more than once across the repository is reported at severity 5, for the developer's information (Refactoring Duplicates). Enforced by
DetectCopyPasteForHtmlandDetectCopyPasteForVisualforce.
Rules¶
The rules that this page mentions. The Standard column gives each rule's standard in one line, and the Section column links to the section that mentions the rule; "Not yet written" marks a rule of this page that no section mentions yet. Appendix F.6 holds the full cross-reference.
| Rule | Kind | Severity | Standard | Section |
|---|---|---|---|---|
Avoid |
Custom PMD XPath | 1 | A Visualforce link does not point to a hard-coded record Id | Resources and Links |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 2 | No private SLDS styling hook, one whose name starts with --_slds- or --slds-s- |
Safe Use of Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 2 | No SLDS linter suppression without an approving review from the owning team | Suppression Comments |
Avoid |
Stock Code Analyzer PMD | 2 | No credential as the default value of an aura:attribute |
Credentials in Markup |
Avoid |
Stock Code Analyzer PMD | 2 | No credential as the default value of an apex:attribute |
Credentials in Markup |
Avoid |
Custom PMD XPath | 2 | No attribute in Lightning markup or plain HTML holds a javascript: URL, except javascript:void(0) |
Error Prevention |
Avoid |
Custom PMD XPath | 2 | No attribute in Visualforce holds a javascript: URL, except javascript:void(0) |
Resources and Links |
Avoid |
Stock Code Analyzer PMD | 2 | A Visualforce page does not read $Api.Session_ID |
Session Ids |
Avoid |
Stock Code Analyzer PMD | 2 | A Visualforce page does not call GETSESSIONID() |
Session Ids |
Layout |
Custom PMD XPath | 2 | A layout table carries no caption, th, summary, headers, or scope Notes on this rule. |
Tables |
Layout |
Custom PMD XPath | 2 | A layout table in a Visualforce page carries no caption, th, summary, headers, or scope Notes on this rule. |
Tables |
Load |
Stock Code Analyzer PMD | 2 | An apex:stylesheet loads a stylesheet from a static resource |
External Scripts and Stylesheets |
Load |
Stock Code Analyzer PMD | 2 | A link tag in a Visualforce page loads a stylesheet from a static resource |
External Scripts and Stylesheets |
Load |
Stock Code Analyzer PMD | 2 | A script tag in a Visualforce page loads JavaScript from a static resource |
External Scripts and Stylesheets |
Load |
Stock Code Analyzer PMD | 2 | An apex:includeScript loads JavaScript from a static resource |
External Scripts and Stylesheets |
No |
Custom Code Analyzer Regex | 2 | No console statement in a Visualforce page or component | Console Statements |
No |
Custom Code Analyzer Regex | 2 | Nothing loads from polyfill.io |
External Scripts and Stylesheets |
No |
Custom Code Analyzer Regex | 2 | The author is a person or a team, never a tool default such as ChangeMeIn@UserSettingsUnder.SFDoc |
Comments |
Server |
Custom PMD XPath | 2 | No image is a server-side image map | Media |
Server |
Custom PMD XPath | 2 | No image in a Visualforce page is a server-side image map | Media |
Vf |
Stock PMD | 2 | A page changes no data when it loads; the action of apex:page calls no method that writes |
Cross-Site Request Forgery |
Vf |
Stock PMD | 2 | A merge expression in a style element is encoded |
Cross-Site Scripting |
Vf |
Stock PMD | 2 | A merge expression is always encoded for the place it is written | Cross-Site Scripting |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 3 | No deprecated --lwc- design token; use the matching SLDS 2 styling hook, with the token as its fallback value |
Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 3 | A modal close button follows the SLDS modal blueprint | Modal Close Button |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 3 | No class that SLDS 2 does not have | SLDS Classes |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 3 | Every SLDS styling hook in var() has a fallback value |
Safe Use of Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 3 | No styling hook that SLDS 2 does not have | Styling Hooks |
Avoid |
Custom PMD XPath | 3 | Lightning markup holds no absolute URL of a Salesforce org | Error Prevention |
Avoid |
Custom PMD XPath | 3 | A Visualforce page holds no absolute URL of a Salesforce org | Resources and Links |
Avoid |
Custom PMD XPath | 3 | Link to a Visualforce page with $Page, not an /apex/ path Notes on this rule. |
Resources and Links |
Avoid |
Custom PMD XPath | 3 | Reference a static resource through $Resource, not a /resource/ path Notes on this rule. |
Resources and Links |
Avoid |
Stock PMD | 3 | Styling lives in a stylesheet, not in an inline style attribute |
CSS |
Avoid |
Custom PMD XPath | 3 | No style element, and no style, labelStyle, or html-style attribute, in a Visualforce page or component |
CSS |
Avoid |
Custom PMD XPath | 3 | A Visualforce page or component has one apex:form |
Standard Components |
Layout |
Custom PMD XPath | 3 | No table is used for layout Notes on this rule. | Tables |
Layout |
Custom PMD XPath | 3 | No table is used for layout in a Visualforce page Notes on this rule. | Tables |
Mouse |
Custom PMD XPath | 3 | Every function that a mouse reaches, a keyboard reaches too | Keyboard |
Mouse |
Custom PMD XPath | 3 | Every function that a mouse reaches in a Visualforce page, a keyboard reaches too | Keyboard |
Nested |
Custom PMD XPath | 3 | A script block does not hold the text of another script tag | Script Blocks |
No |
Custom PMD XPath | 3 | An event attribute in Aura markup names a controller action, not a script | Lightning Web Components and Aura: Scripts and Resources |
No |
Custom PMD XPath | 3 | A Visualforce page or component should not use Lightning Out (beta) | External Scripts and Stylesheets |
Video |
Custom PMD XPath | 3 | A video has a track of captions or subtitles |
Media |
Video |
Custom PMD XPath | 3 | A video in a Visualforce page has a track of captions or subtitles |
Media |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | An SLDS class modifier takes a single underscore, as in slds-button_brand, not a double dash |
SLDS Classes |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | A component styling hook has its current SLDS 2 name | Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | No legacy --sds- styling hook; use the --slds- hook of the same name |
Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | Colours, spacing, and type sizes come from SLDS 2 styling hooks, not from hard-coded values | Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | No override of an SLDS class; give the component a class of its own | SLDS Classes |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | A custom styling hook has a name in the component's own namespace, not in --slds- or --sds- |
Safe Use of Styling Hooks |
@salesforce-ux/ |
Stock Code Analyzer ESLint | 4 | An --slds- styling hook is never the fallback value of an --lwc- design token |
Styling Hooks |
Avoid |
Custom PMD XPath | 4 | A Visualforce page does not frame another page | Standard Components |
Fieldset |
Custom PMD XPath | 4 | A fieldset has a legend that names its group of controls |
Form Groups, Frames, and Lists |
Fieldset |
Custom PMD XPath | 4 | A fieldset in a Visualforce page has a legend that names its group of controls |
Form Groups, Frames, and Lists |
Frame |
Custom PMD XPath | 4 | An iframe or a frame has a title that says what it holds |
Form Groups, Frames, and Lists |
Frame |
Custom PMD XPath | 4 | An iframe, a frame, or an apex:iframe in a Visualforce page has a title that says what it holds |
Form Groups, Frames, and Lists |
List |
Custom PMD XPath | 4 | A list item is inside a list: an li in a ul, ol, or menu, and a dt or dd in a dl |
Form Groups, Frames, and Lists |
List |
Custom PMD XPath | 4 | A list item in a Visualforce page is inside a list | Form Groups, Frames, and Lists |
Object |
Custom PMD XPath | 4 | An object element holds fallback content |
Media |
Object |
Custom PMD XPath | 4 | An object element in a Visualforce page holds fallback content |
Media |
Repeated |
Custom PMD XPath | 4 | Landmarks with the same role have different labels | Landmarks |
Repeated |
Custom PMD XPath | 4 | Landmarks with the same role in a Visualforce page have different labels | Landmarks |
Table |
Custom PMD XPath | 4 | A headers attribute names only the id of a th in the same table Notes on this rule. |
Tables |
Table |
Custom PMD XPath | 4 | A headers attribute in a Visualforce page names only the id of a th in the same table Notes on this rule. |
Tables |
Table |
Custom PMD XPath | 4 | A header cell has a scope, or an id that cells name in their headers attribute |
Tables |
Table |
Custom PMD XPath | 4 | A header cell in a Visualforce page has a scope, or an id that cells name in their headers attribute |
Tables |
Table |
Custom PMD XPath | 4 | A data table says what it holds, in a caption or an ARIA label |
Tables |
Table |
Custom PMD XPath | 4 | A data table in a Visualforce page says what it holds, in a caption or an ARIA label |
Tables |
Table |
Custom PMD XPath | 4 | A data table marks its header cells with th |
Tables |
Table |
Custom PMD XPath | 4 | A data table in a Visualforce page marks its header cells with th |
Tables |
Visualforce |
Custom PMD XPath | 4 | A custom controller's name ends in Controller, and a controller extension's in Extension or Ext Notes on this rule. |
General: Controller Names Metadata Standards: Naming |
Visualforce |
Custom Code Analyzer Regex | 4 | A Visualforce page or component starts with a comment that says what it is for | Comments |
Detect |
Stock Code Analyzer CPD | 5 | No copied blocks of HTML markup; share a component | Duplicated Markup |
Detect |
Stock Code Analyzer CPD | 5 | No copied blocks of Visualforce markup; share a component | Duplicated Markup |
No |
Custom Code Analyzer Regex | 5 | No TODO, TO-DO, FIXME, FIX-ME, or HACK stays in a markup comment |
Comments |