Skip to Content
Technical Articles
Author's profile photo Srdjan Boskovic

Powerful web applications with old and new ABAP systems

ABAP RFC protocol is the fastest way of data exchange between ABAP system and ABAP or non-ABAP system or application. The protocol is available in all on-premise ABAP installations, old and new, and in cloud-based ABAP systems, like SAP S/4HANA Cloud or Steampunk. It is enabled in SAP Cloud Connector for Java runtime and for certain scenarios in NodeJS and Python run-times.

Over decades tons of ABAP business logic is encapsulated in RFC-enabled ABAP Function Modules and BAPIs, many of which already exposed via ODATA services added on top. Nevertheless, RFC is still used in scenarios when larger data volumes shall be moved between ABAP systems, because of unmatched performance and availability “everywhere”. Or for rapid and real prototyping of ERP or S4H BTP extensions.

Web applications with ABAP RFM API

In this blog we cover one less common scenario, using RFC protocol to expose ABAP business logic and direct consume it by on-premise or cloud web applications, built with web framework of choice (configurable by developer).

In this direct mix of “plain ABAP” and modern web, one the same ABAP data model is exposed and used at all application layers, from ABAP backend to express and React for example. It accelerates the development and troubleshooting and results in apps with by factors less code and unique capabilities, impossible to achieve using other technologies. Apps performance is like clickable demo while working with real ABAP systems and no hourglass has been used yet.

ABAP development is done like the frontend will be built with ABAP and no additional efforts or web skills are required for ABAP RFM API development. The same is with web development, no special SAP/ABAP skills are required for web app development with ABAP RFM backend. RFM stands for Remote Enabled ABAP Function Module.

When ABAP data model is exposed in frontend, what is missing are metadata or annotations, like  data type, length, field texts etc., so that the frontend can handle it accordingly. This gap is solved in design-time, using command line tool abap, part of SAP/fundamental-tools open source.

Using abap utility, HTML/JS snippets of annotated ui elements with ABAP bindings are generated for ABAP RFM API fields, like <ui-input>, <ui-checkbox>, <ui-date-picker> etc.. App developer can copy/paste/adapt these ui elements into pages and layouts and frontend logic, per application requirements.

Annotated HTML/JS snippers shall represent real ui elements, capable to “understand” and handle annotations. One and only set of real ui elements implemented so far, is built with Aurelia web framework and easy portable to other web frameworks.

This solution design is used in prototyping of powerful cloud business applications with ABAP backend systems and limits of functional/visual scalability are not reached yet.


Frontend development

Application components are shown on deployment diagrams below and how to build them is illustrated with one real-life application example.

Deployment options

Web applications consuming ABAP RFMs can be deployed on-premise or in the cloud. Connection parameters in the cloud are different then on-premise and cloud services, like audit log for example, are not available on premise. The application code is otherwise the same.

RFC connectivity from on-premise web application to ABAP system requires platform specific RFC connector: Java SAP JCo, Microsoft SAP NCo, Python SAP/PyRFC  or Node.JS: SAP/node-rfc.

The diagram below shows de-facto standard web application, with View-Model, View and Server, and ABAP RFM API.


Deployment on-premise

RFC connectivity from cloud Java applications is supported by SAP Cloud Connector. The same is for Node.JS and Python enabled only for certain usage scenarios, supported by SAP development. The usage scenario shall be therefore aligned with SAP development or custom buildpack can be used for prototyping for example.


Deployment in cloud

The custom buildpack includes SAP NW RFC SDK binaries, added to LD_LIBRARY_PATH. The application can use WebSocket RFC connection parameters, to reach ABAP on-premise system with WebSocket RFC support, outside company firewall. If WebSocket RFC protocol is not supported by ABAP system, or connection outside firewall not wanted, the SAP Business Connector shall be installed on-premise, inside the firewall. For more info check SAP blogs

Let now build app components and put them together following above mentioned approach: ABAP API, App Server, View Model and View.


Before opening ABAP editor, a good idea is to clarify requirements, thus let start with that first 🙂


Our app shall provide an alternative to ABAP Dynpro transactions IE03 and IE02, offering SAP Business Object Equipment read and edit, with three customer specific requirements:

  • Read and edit grouped and ungrouped Classifications and Characteristics associated with Equipment instance
  • Display Mean Time to Repair (MTTR) and Mean Time Between Failure (MTBF) statistics, relevant in certain business scenarios
  • Attach (and remove) documents like PDF, AutoCAD drawings etc. to Equipment instance, using the Document Management System (DMS)

SAP business object Equipment represents physical assets like the office furniture, devices, machineries etc. Using Classifications and Characteristics, name/value pairs can be added to business object instance, further describing the object. Similar characteristics (name/value pairs) can be grouped under the same group name and several such groups plus ungrouped characteristics may be maintained for Equipment object instance.

Screenshots below show ABAP Dynpro screen and corresponding web application, as per above requirements. The app can be tested from Walldorf VPN only: coevi76/plm3


Dynpro screen


Web application screen

Based app requirements, the business logic is localised mostly in already existing BAPIs, then wrapped into RFMs, to provide more flexibility and capabilities like reading MTTTR/MTBF times. Here are some basic rules how to expose ABAP business logic via ABAP RFM API, as per experience from real-life projects,

ABAP RFM names are for now only saved in local file, so that ui elements can be generated later on, in step 4 (View).

  - /COE/RBP_PAM_EQUIPMENT_GETL # Selection of Equipment List
  - /COE/RBP_PAM_EQUIPMENT_GET # Read Equipment
  - /COE/RBP_PAM_EQUIPMENT_CHANGE # Change Equipment
  - /COE/RBP_PAM_EQUIPMENT_INSTALL # Install Equipment (Functional Location, Superior Equipment)
  - /COE/RBP_PAM_EQUIPMENT_DISMTLE # Dismantle Equipment (Functional Location, Superior Equipment)
  - /COE/RBP_PAM_EQUIP_HIER_GET # Read Equipment installation hierarchy
  - BAPI_EQUI_CREATE # Create Equipment

App Server

ABAP RFM API data are exposed via app server routes. App server pattern depends on ABAP API and app requirements. In this case no additional logic is required on server and ABAP data are just passed forth and back, without orchestration.

The example below is implemented with Python Flask server and looks almost identical in Node.JS express or Java. Data mappings work the same way as well:

  • Python variables represent ABAP variables
  • Python dictionary (like plain Node.JS or Java object) represents ABAP structures
  • Arrays of Python dictionaries represent ABAP tables

ABAP to Python/Node/Java data conversions are done automatically, inside RFC connector.

Here the app server code snippet for Equipment routes and here full source code server/

# Equipment
@app.route('/equipment/<path:path>', methods=['POST'])
def equipment(path):
        to_abap = json.loads(

        if path == 'get':
            from_abap ='/COE/RBP_PAM_EQUIPMENT_GET', to_abap)
        elif path == 'getlist':
            from_abap ='/COE/RBP_PAM_EQUIPMENT_GETL', to_abap)
        elif path == 'change':
            from_abap ='/COE/RBP_PAM_EQUIPMENT_CHANGE', to_abap)
        elif path == 'install':
            from_abap ='/COE/RBP_PAM_EQUIPMENT_INSTALL', to_abap)
        elif path == 'dismantle':
            from_abap ='/COE/RBP_PAM_EQUIPMENT_DISMTLE', to_abap)
            raise Exception ('not implemented: %s' % path)

        return to_json(from_abap)

    except Exception, e:
        return serverError(e), 50

Let trace one full HTTP request/response cycle, using equipment/get route, to read Equipment data from ABAP system.

The frontend works with ABAP RFM data in JavaScript format and sends the equipment number as ABAP API IV_EQUIID parameter in HTTP POST request:

POST coevi76/plm3/api/equipment/get
Content-Type: application/json

{"IV_EQUIID": 4711}

Using json.loads(), the HTTP request data are transformed to Python dictionary {“IV_EQUIID”: 4711} and sent to ABAP RFM PAM_EQUIPMENT_GET, to read Equipment data.

The RFC connector, encapsulated in, automatically transforms Python request data to ABAP, invokes PLM_EQUIPMENT_GET and returns ABAP Equipment data to Python variable result. The result is sent in HTTP response to web browser, using to_json(result).

In this scenario

  • ABAP data go all the way up to app server and frontend, thus all application layers work with the same ABAP data model. App server works with ABAP data in Python, frontend with ABAP data in JavaScript and ABAP works with ABAP 🙂
  • It considerably simplifies the development, troubleshooting and communication among ABAP and Web developers
  • The programming model is “standard” ABAP, like the frontend is built in ABAP. It is a nice opportunity for ABAP developers to leverage ABAP knowledge and implement some server logic in Java, Node.JS or Python app server.
  • Application is ABAP stateful by default (configuration), thus COMMIT BAPI shall be invoked after CHANGE BAPI for example. In this example BAPIs are wrapped into application specific RFMs, including COMMITs but COMMIT can be also explicit, depending on scenario.
  • ABAP API adaptations, extensions, choreography, orchestration, caching etc. can be added at server level, if needed, to cover industry or customer specific requirements
  • The server logic sometimes need access to ABAP data structures at field level, in which case abap call template can help.

View Model (JS)

Via server routes, ABAP data, like Equipment instance, reach the frontend View-Model. The programming language is now JavaScript and the orchestration can be done also at this level, the ABAP way. Calling BAPI COMMIT after BAPI CHANGE for example is here still possible, via server routes now.

The snippet below shows the frontend logic of Equipment class get() request, copied from the full source code: client/src/plm/equipment/model.js

get(id = null) {
  if (id) this.selection.EQUIID = id;
  return this.httpService
    .backend("/equipment/get", {
      IV_EQUIID: this.selection.EQUIID,
      IV_DOCUMENT: "X",
    .then((FROM_ABAP) => {
      // header

      // clone structures for X-fields change detection
      this.IS_HEADER = UIUtils.abapStructClone(this.ES_HEADER);
      this.IS_SPECIFIC = UIUtils.abapStructClone(this.ES_SPECIFIC);

      // user status

      // DMS Attachments
      this.Attachments = FROM_ABAP.ET_DOCUMENT;

      //// get the image
      //if (image) {
      //  this.httpService.backend(`/document/download/${image.FILE_ID}?mime_type=${image.MIMETYPE}`)
      //    .then(IMAGE => {
      //      //this.Image = {
      //      //  imageMimeType: mimeType,
      //      this.image.content = IMAGE.EV_CONTENT;
      //      //}
      //    })
      //    .catch(error => {
      //    });

      // its url
      this.href ="IE03", false, this.selection.EQUIID);
      this.hrefText =
        this.IS_HEADER.DESCRIPT + " (" + this.selection.EQUIID + ")";

      // Characteristics todo: optimize
      this.Characteristics = [];

      // add grouped characteristics
      for (let chGroup of FROM_ABAP.ET_CHARACT_GROUP) {

      // add ungrouped characteristics
      this.addCharactGroup(FROM_ABAP.ET_CHARACTERISTICS, "", "Ungrouped");

      // let the View update
      //`Equipment found: ${this.selection.EQUIID}`);

      // mttr/mtbf kpi
      this.KPI = [];
      if (this.IS_HEADER.START_FROM) {
        if (FROM_ABAP.ET_RESULT.length) {
          this.KPI = [
              id: "BMON",
              name: "No. of breakdowns reported in month",
              value: "",
            { id: "TBR", name: "Time between repairs", value: "" },
            { id: "BACT", name: "No. of actual breakdowns", value: "" },
            { id: "MTBR", name: "Mean time between repairs ", value: "" },
              id: "TLDM",
              name: "Total length of downtime in month",
              value: "",
            { id: "MTTRM", name: "Mean Time To Repair in Month", value: "" },
          let result = FROM_ABAP.ET_RESULT[0];
          for (let line of this.KPI) {
            if ( === "BMON") line.value = result.NBDEFF;
            else if ( === "TBR") line.value = result.STBRHRS;
            else if ( === "BACT") line.value = result.SNBDREP;
            else if ( === "TLDM") line.value = result.STTRHRS;
          for (let line of this.KPI) {
            if ( === "MTBR")
              if (result.SNBDREP) line.value = result.STBRHRS / result.NBDEFF;
            if ( === "TLDM")
              if (result.SNBDREP) line.value = result.STBRHRS / result.SNBDREP;
            if ( === "MTTRM")
              if (result.NBDEFF) line.value = result.STTRHRS / result.NBDEFF;
            if ( !== "BMON" && !== "BACT") {
              line.value += " H";

    .catch((error) => {

addCharactGroup(ET_CHARACTERISTICS, groupId, groupName) {
  let chlist = [];

  // add group
  for (let ch of ET_CHARACTERISTICS) {
    if (ch.CHARACT_GROUP === groupId) {
      ch.NUMBER_DIGITS -= ch.NUMBER_DECIMALS; // adapt for MNUMBER :-)

  if (chlist.length) {
      groupId: groupId ? groupId : "GROUPID",
      groupDesc: groupName,
      CHARLIST: chlist,

save() {
  // structures are identical, set X-fields for header and specific
  let header = [
  let specific = ["EQUICATGRY", "READ_FLOC"];
  this.IS_HEADER_X = UIUtils.abapStructDiff(
  this.IS_SPECIFIC_X = UIUtils.abapStructDiff(
  // characteristics
  let itChar = [];
  for (let chGroup of this.Characteristics) {
    for (let ch of chGroup.CHARLIST) {
        CLASS: ch.CLASS,
        CHARACT: ch.NAME_CHAR,
        VALUE: ch.VALUE,
        VALUE_TO: ch.VALUE_TO,

  return this.httpService
    .backend("/equipment/change", {
      IV_EQUIID: this.selection.EQUIID,
      IS_HEADER: this.ES_HEADER, // changed data in ES_
      IS_HEADER_X: this.IS_HEADER_X,
      IS_DATA_SPECIFIC: this.ES_SPECIFIC, // changed data in ES_
    .then((FROM_ABAP) => {{
        type: "success",
        message: `Equipment saved: ${this.selection.EQUIID}`,
    .catch((error) => {;

Inspecting view model source code probably the first thing to notice is it is less than 200 lines long, despite not quite simple requirements. Here are few more observations and experience from projects:

  • The code comprises almost exclusively of application logic, no traces of frameworks specific or technical components, hindering developer’s view of application. Each web frameworks brings own “artefacts” more or less but developers are happier and more productive with less “verbose” frameworks. Aurelia is used here because of the minimum of such overhead.
  • The same ABAP RFM API data are used in frontend and ABAP developers recognise well known “X fields” in save() method for example. ABAP developers feels therefore also here “at home” and can do the View Model development. It is just like ABAP development, same data structures, only in JavaScript/TypeScript and no HTML/CSS knowledge mandatory
  • Just like app server, the view-model deals with ABAP data on detail/field level only when necessary, keeping the code short and readable. ABAP RFM API can operate with data structures with hundreds of fields. They are all there but exposed in code only when necessary, which simplifies the development and troubleshooting and improves maintainability.


HTML or JS View comprises of annotated ui elements, bound to View-Model data, thus ABAP data in JavaScript. When View Model data are updated, per equipment/get route response for example, the ui elements are automatically updated, When ui element updated by user input, the underlying View Model data are updated automatically, thanks to bi-directional binding.



Now is the time to use the local file created in first step and generate ui elements’ for ABAP RFM API, using abap CLI get and make commands:

npm install -g abap-api-tools

# run after ABAP RFM API changed, conection to ABAP system required
abap get MME -c config/ 

# no ABAP system connection needed
abap make aurelia -c config/

It creates HTML/JS snippets of Aurelia ui elements, with ABAP RFM data bindings and annotations:

  • Data type, length
  • Texts (label, caption)
  • Unit of measure
  • Value Input Help
    • Field Domain Values
    • Check Tables and custom lookups
    • Elementary and complex F4 search helps
  • Default value (ABAP SU3 SET/GET parameter)
  • Conversion Exit

The output are plain lists of ui elements, for each field and table of ABAP RFM API, like pam_equipment_get.html and pam_equipment_get.js. HTML file contains HTML snippets of annotated ui element and JS file contains ABAP RFM API data structures initialisers, helpful in certain scenarios.

Few remarks also here:

  • Using abap utility is not mandatory. Ui elements bound to ABAP RFM API data can be manually implemented and generated ui elements can be adapted as needed. Attributes like SU3 defaults, value input helps etc., can be arbitrary added or removed from any ui element, technical format can be changed etc
  • With basic HTML/CSS skills, ABAP developers can build also this layer, when no more then copy.paste/group of ui elements needed.
  • HTML and JS snippets expose “expanded” ABAP API structures, with all fields and no frontend application needs all fields. They cover all scenarios for all industries, customer segments etc. The fields relevant for front-end depend on business scenario and usually defined by SAP functional expert. Only these fields` HTML snippets are used in particular app front-end view.
  • The generated HTML UI elements impose no restrictions on view layout and styling, they are fully independent

ABAP Value Helps

All ABAP Value Helps work out of the box, using generic abap-value-help API and frontend dialog component:

  • ABAP Fixed Domain Values (FV)
  • ABAP Elementary and complex Search Helps (SH)
  • ABAP Check Tables (CT, CH)
  • Custom value helps

The implementation of these components is also low-code and described in this blog.

ABAP Value Help is enabled by custom shlp attribute, automatically added by abap make generator to ui element HTML snippet. When not defined in ABAP system, the attribute can manually added to any input element. The default Value Help can be also manually replaced with another standard or custom Value Help.

<!-- Fixed Domain Values -->
<ui-input data-abap.bind="{ddic:'CHAR', type:'string', length:1}" label="Partial/Final Confirmation"
  shlp.bind="{type:'FV', id:'AUERU_VS'}"

<!-- Check Tables -->
<ui-combo clear ddic-length="1" ddic-type="CHAR" mid="EQT" label="Equipment category"
  shlp.bind="{type: 'CT', id: 'T370T'}"

<!-- Complex/Elementary Helps Search Helps -->
<ui-input ddic-length="18" ddic-type="CHAR" label="Equipment #"
  shlp.bind="{type: 'SH', id: 'EQUI', blacklist: 'SH EQUIR', autoselect: 'SH EQUIT'}"

Low-code by design

Fully functional app is implemented with ca. 400 lines of code, which looks surprising little onsidering capabilities:

  • ABAP: the pure ABAP business logic exposed, without technical overhead
  • App server: ca. 20 lines of Python
  • View-Model: ca. 190 lines of JavaScript
  • Front-end view: ca. 150 lines of HTML

The low-code is here achieved by solution design simplification (until nothing left to remove …) and using the same data model at each level: frontend/server/backend. Standard web best-practices are combined with classical ABAP best practices, without “mediators” in-between and play surprisingly well together.

Comparisons with similar applications using other technologies show around 40-50 times less code required at ABAP level, because of using plain ABAP business logic, without overhead. Around 10-20 times less code is required at Web level, by straightforward ABAP data consumption, with ui components enriched in design-time by ABAP metadata.

The simplicity of this approach makes the app development fun because results come up quickly and functional/visual variations are easy to do.

The implementation is under full developer’s control, without any “magic” added by abap CLI and without run-time dependencies of this toolkit, except for inevitable Value Helps.


Alhough already powerful, the Equipment maintenance app is far from limits of what this approach can handle.

When developed this way, more apps can be assembled into so called super-apps, without noticable impact on performance or maintainability.

The example below is probably unique case implemented with SAP technologies, a cockpit super-app, assembled from Service Notifications, Service Orders and Equipment Maintenance “elementary” apps, all without iframes: client/src/plm/super-app/app.html

Presentation Layer

Using this solution design, the presentation layer with any web design system can be applied.

Fiori design system is provided by SAP Fundamental Library Styles and Fiori styled Angular, React Vue and Aurelia ui components. SAP Fundamental Library Styles is independent and can be used with other ui frameworks as well. Also different design system can be used, like Microsoft FAST for example, which is a matter of abap-api-tools configuration.

The showcase app has been implemented using Aurelia ui components with custom design system, before SAP Fundamental Library Styles were completed. It is clearly visible in live demo. Aurelia components use Fiori styles now, provided by SAP Fundamental Styles.

CAP and SAP Cloud SDK

CAP, SAP Cloud SDK and similar frameworks relate to App Server component of this solution design. App Server can be implemented using these frameworks, like using Nest.JS scaffold of SAP Cloud SDK for example, instead of Node.js express used in above example. The blog describes “nothing to remove” minimum, with plain express or Flask app server. It is simple but does the job for rapid functional/visual prototyping.


Hope you enjoyed reading and stay tuned for more updates.

Contributions to SAP/fundamental-tools project are welcome and please feel free to share your thoughts, questions and suggestions.





Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Nils Lutz
      Nils Lutz

      Hi Srdjan Boskovic

      why no OData as this should be the prefered way like for S/4HANA? Why no UI5 which is designed with OData (or at least REST) in mind?

      Why building "Super-Apps" when Fiori explicitly tells us to divide and conquer (1-1-3 principle)?

      Best Regards,


      Author's profile photo Srdjan Boskovic
      Srdjan Boskovic
      Blog Post Author

      Hello Nils,

      ODATA is of course preferred way for production development.

      As mentioned in the blog, the approach is not used in production but prototyping and research of functionally powerful applications, hard or impossible to build with ODATA, like super-app cockpit, especially with old ABAP systems

      To my understanding the 1-1-3 is general recommendation, not all power user applications fit into.

      Kind regards, Srdjan

      Author's profile photo Nils Lutz
      Nils Lutz

      Ok i understand that this is a approach for prototyping. But why would one prototype an application or solution with a technology which is not strategic for SAP? Like prototyping with CAP/RAP is at least as fast as the described approach and on top it is fully supported by SAP. And for the user interface part the whole S/4HANA UI/UX strategy depends on the Fiori Design System. So going with your approach would further fragment and confuses the user and developer community as what is "the way" now in S/4HANA world.

      Last but not least i hardly doubt that such a super-app is impossible to build with OData, maybe there would be more than one OData needed to achieve the underlying data layer but thats far away from impossible 😀


      Kind regards,


      Author's profile photo Srdjan Boskovic
      Srdjan Boskovic
      Blog Post Author

      Hi Nils,

      the Fiori design system is actually supported, using SAP Fundamental Library Styles. It is mentioned in README, also ui5 and ui5-react. The live example described in the blog does not reflect it because built before Fundamental Styles were ready. Current Aurelia components use Fiori design but it is not the only design system which comes up in prototyping, There was a request with SAP internal design system for example.

      why would one prototype an application or solution with a technology which is not strategic for SAP

      Prototype helps discover and solve critical challenges, for the fraction of time and efforts of building production solution. The cost of prototype modifications must be also low because the uncertainty and change rate are initially high - functional/visual variations are explored "on the go".

      The most of projects are done with standard stack but with tight deadlines and budget and especially in older systems, this approach makes the "shortcut". It exposes the "core" business logic, below technical layers which may be missing in older systems. It takes the absolute minimum of efforts on ABAP side, also because no perfect API normalisation is required. Once the solution is acknowledged, the standard layers shall be added (reason for migration) or custom solution can be built using prototype as a live blueprint.

      Another reason for using it in prototyping is that frontend described in a blog is built in 1 day (after ABAP part done), with all Value Helps working out of the box, in a simple and flexible way. It  reduces prototyping efforts because Value Helps require more work: 258917, 262469, 291736

      Last but not least i hardly doubt that such a super-app is impossible to build with OData

      I agree, the statement is perhaps too categorical, perhaps we see another blog tomorrow with even heavier app 🙂 I only never seen similar case, with few "heavier" apps combined into single power-user cockpit, especially without performance decrease

      Hope this info on context helps 🙂



      Author's profile photo Sandra Rossi
      Sandra Rossi

      For my (and possibly others) culture, can't find info, what means 1-1-3? (no reference in Divide-and-conquer algorithm - Wikipedia)

      Author's profile photo Nils Lutz
      Nils Lutz

      1 Role 1 Use Case 3 Screens as a rule of thumb for designing new Fiori apps

      Author's profile photo Srdjan Boskovic
      Srdjan Boskovic
      Blog Post Author

      I could not find a reference to 1 1 3 here

      @Nils, do you maybe have a link?

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thanks a lot!

      What is SAP Fiori? Maybe it’s more than you think | SAP Blogs

      • « SAP advocate a design principle known as 1-1-3 (“one one three”). This means each screen should be designed with a single user (or role) in mind, a single task that this user wants to accomplish, and a maximum of three levels of navigation to perform this task. »
      • « The SAP Fiori UX follows a simple 1-1-3 mantra: 1 user, 1 use case, 3 screens. This reduces applications to only what is important for a ... »
      • « ... fiori ux Fiori apps emphasize a 1:1:3 approach: one user, one use case, and three screens (desktop, tablet, and mobile). »

      I'd say that SAP official vision is more "5" than "1-1-3" but no need nitpicking 😉 The Fiori Design Principles | SAP Blogs, SAP Design – SAP User Experience CommunityDesign Principles | SAP Fiori for Web Design Guidelines